MyBatis持久层框架
第1章 Mybatis框架入门
1.1 Mybatis简介
MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下, iBatis3.x正式更名为MyBatis。代码于2013年11月迁移到Github。
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
官网地址:https://mybatis.org/mybatis-3/zh_CN/index.html
小结
- Mybatis是半自动持久化层ORM框架
- 半自动:Mybatis(研发效率较低,程序执行效率较高)
- 全自动:Hibernate(研发效率高,程序执行效率低)
- ORM(Object Relational Mapping):对象关系映射
- 将对象中属性与表中字段建立映射关系,好处:操作对象会影响表中的数据
1.2 持久化层框架对比
- JDBC
- SQL 夹杂在Java代码中耦合度高,导致硬编码内伤
- 维护不易且实际开发需求中 SQL 有变化,频繁修改的情况多见
- 代码冗长,开发效率低
- Hibernate 和 JPA
- 操作简便,开发效率高
- 程序中的长难复杂 SQL 需要绕过框架
- 内部自动生成的 SQL,不容易做特殊优化
- 基于全映射的全自动框架,大量字段的 POJO 进行部分映射时比较困难。
- 反射操作太多,导致数据库性能下降
- MyBatis
- 轻量级,性能出色
- SQL 和 Java 编码分开,功能边界清晰。Java代码专注业务、SQL语句专注数据
- 开发效率稍逊于 Hibernate,但是完全能够接收
开发效率:Hibernate > Mybatis > JDBC
运行效率:JDBC > Mybatis > Hibernate
1.3 Mybatis之Helloworld(快速入门)
1.3.1 准备环境
-
准备数据模型
CREATE DATABASE `db_mybatis`;USE `db_mybatis`;CREATE TABLE `t_emp`(emp_id INT AUTO_INCREMENT,emp_name CHAR(100),emp_salary DOUBLE(10,5),PRIMARY KEY(emp_id) );INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("tom",200.33); INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("jerry",666.66); INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("andy",777.77);
-
新建工程:springboot环境下
1.3.2 导入依赖
-
springboot版
<dependencies><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.3</version></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter-test</artifactId><version>3.0.3</version><scope>test</scope></dependency> </dependencies>
-
纯享版(了解)
<dependencies><!-- mybatis依赖 --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.11</version></dependency><!-- MySQL驱动 mybatis底层依赖jdbc驱动实现,本次不需要导入连接池,mybatis自带! --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.25</version></dependency><!--junit5测试--><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.3.1</version></dependency> </dependencies>
1.3.3 配置文件
spring.application.name=day18_myabtis_s2
#数据库连接池配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/db_mybatis
spring.datasource.username=root
spring.datasource.password=root
#mybatis配置
#设置映射文件位置
mybatis.mapper-locations=classpath:mappers/*.xml
#开启下划线转驼峰(自动映射)
mybatis.configuration.map-underscore-to-camel-case=true
#设置别名包(为当前包所有对象设置别名:类名(忽略大小写))
mybatis.type-aliases-package=com.atguigu.pojo
#添加日志信息
logging.level.com.atguigu.mapper=debug
1.3.4 Mapper接口
Mybatis中不用为Mapper接口设置实现类,框架会自动为Mapper生成代理对象
注意:需要在接口上添加@Mapper注解,才会生成代理对象
package com.atguigu.mapper;import com.atguigu.pojo.Employee;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;import java.util.List;//@Repository
@Mapper // 表示这是一个mybatis的mapper接口(springboot底层会为EmployeeMapper接口创建代理类)
public interface EmployeeMapper {/*** 查询所有员工信息*/List<Employee> selectAllEmps();}
1.3.5 Mapper映射文件
Mapper映射文件中需要注意以下特点:
- 映射文件名称与接口名称一致
- 映射文件中标签中namespace与接口的全类名一致
- 映射文件中id属于与接口中方法名一致
- 方法返回值和resultType属性值一致
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""https://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.atguigu.mapper.EmployeeMapper"><select id="selectAllEmps" resultType="employee">SELECTe_id,e_name,salary,address,d_idFROMt_emp</select>
</mapper>
1.3.6 测试
package com.atguigu;import com.atguigu.mapper.EmployeeMapper;
import com.atguigu.pojo.Employee;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.util.List;@SpringBootTest
class Day18MyabtisS2ApplicationTests {@Autowiredprivate EmployeeMapper employeeMapper;@Testvoid contextLoads() {List<Employee> employees = employeeMapper.selectAllEmps();for (Employee employee : employees) {System.out.println("employee = " + employee);}}}
1.4 Mybatis映射文件详解
官网地址:https://mybatis.org/mybatis-3/zh_CN/sqlmap-xml.html
MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。
SQL 映射文件只有很少的几个顶级元素,常用子标签如下:
- insert标签:定义添加SQL语句
- update标签:定义修改SQL语句
- delete标签:定义删除SQL语句
- select标签:定义查询SQL语句
- resultMap:自定义映射(映射表中字段与类中属性关系)
第2章 Mybatis数据输入
2.1 单个普通参数
- Mybatis中单个普通参数,是可以任意入参(参数名)
2.2 多个普通参数
Mybatis中传递多个普通参数时,底层会封装Map,Map的key是**[参数名列表或param1,param2,param…**](springboot整合mybatis)
Mybatis原生框架,封装底层Map的key是[args0,args1…或param1,param2…]
-
案例代码
/*** 测试多个普通参数 */ Employee selectEmpByProParam(int eId, String eName);
<select id="selectEmpByProParam" resultType="employee">SELECTe_id,e_name,salary,address,d_idFROMt_empwheree_id = #{eId}ande_name = #{param2}
2.3 命名参数
Mybatis中使用@Param注解为参数命名,底层会封装Map,Map的key是:[命名参数名列表或param1,param2…]
-
案例代码
/*** 测试多个普通参数 */ Employee selectEmpByParam(@Param("empId") int eId, @Param("empName") String eName);
<select id="selectEmpByParam" resultType="employee">SELECTe_id,e_name,salary,address,d_idFROMt_empwheree_id = #{empId}ande_name = #{param2} </select>
2.4 POJO入参
Mybatis中使用POJO入参时,参数名就是属性名,且必须getXXX()方法
-
案例代码
/*** 添加员工信息 */ void insertEmployee(Employee employee);
<insert id="insertEmployee">insert intot_emp(e_name,salary,address,d_id)values(#{eName},#{salary},#{address},#{dId}) </insert>
2.5 Map入参
Mybatis中Map入参时,Map的key与参数名称一致(如不一致,不会报错,但无法成功入参)
-
案例代码
/*** 测试Map参数 */ List<Employee> selectEmpByMap(Map<String,Object> map);
<select id="selectEmpByMap" resultType="employee">SELECTe_id,e_name,salary,address,d_idFROMt_empwheree_id = #{eId}ande_name = #{eName} </select>
package com.atguigu;import com.atguigu.mapper.EmployeeParamMapper; import com.atguigu.pojo.Employee; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest;import java.util.HashMap; import java.util.List; import java.util.Map;/***/ //@SpringBootTest(classes = Day18MyabtisS2Application.class) @SpringBootTest public class TestEmployeeParamMapper {@Autowiredprivate EmployeeParamMapper employeeParamMapper;@Testpublic void testSelectEmpByTableName() {//测试单个普通参数 // Employee employee = employeeParamMapper.selectEmpBySingleParam(6); // System.out.println("employee = " + employee);//测试多个普通参数 // Employee employee = employeeParamMapper.selectEmpByProParam(6, "zs"); // System.out.println("employee = " + employee);//测试@Param() // Employee employee = employeeParamMapper.selectEmpByParam(6, "zs"); // System.out.println("employee = " + employee);//测试Pojo入参 // employeeParamMapper.insertEmployee(new Employee(null, "wangwu", 15000, "bj", 2));//测试Map入参Map<String, Object> map = new HashMap<>();map.put("eId",6);map.put("eName","zs");List<Employee> employees = employeeParamMapper.selectEmpByMap(map);for (Employee employee : employees) {System.out.println("employee = " + employee);}}}
2.6 #与$区别(面试题)
2.6.1 #与$底层区别
#{}底层使用PreparedStatement对象,使用?占位符入参,防止SQL注入安全隐患,相对安全
${}底层使用Statement对象,使用字符串拼接方式入参,未防止SQL注入,存在安全隐患,相对不安全
2.6.2 #与$使用场景区别
#{}占位符使用位置:?位置
- select 列名列表 from 表名列名 where 字段=? group by 分组列名 having 字段=? order by 排序列名 limit ?,?
KaTeX parse error: Expected 'EOF', got '#' at position 8: {}使用位置:#̲{}占位符解决不了的位置,使用{}
-
案例
第3章 Mybatis数据输出
3.1 返回单个字面量
Mybatis返回单个字面量数值时,在映射文件中resultType属性直接使用字面量类型或对应别名即可
-
Mybatis底层默认自带别名如下:
别名 映射的类型 _byte byte _char (since 3.5.10) char _character (since 3.5.10) char _long long _short short _int int _integer int _double double _float float _boolean boolean string String byte Byte char (since 3.5.10) Character character (since 3.5.10) Character long Long short Short int Integer integer Integer double Double float Float boolean Boolean date Date decimal BigDecimal bigdecimal BigDecimal biginteger BigInteger object Object date[] Date[] decimal[] BigDecimal[] bigdecimal[] BigDecimal[] biginteger[] BigInteger[] object[] Object[] map Map hashmap HashMap list List arraylist ArrayList collection Collection iterator Iterator -
案例代码
/*** 查询所有员工数量 */ Integer selectEmpCount();
<select id="selectEmpCount" resultType="int">selectcount(1)fromt_emp </select>
3.2 返回单个POJO
resultType作用:期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。
-
案例代码
/*** 测试返回单个POJO */ Employee selectEmpByeId(int eId);
<select id="selectEmpByeId" resultType="employee">selecte_id,e_name,salary,address,d_idfromt_empwheree_id = #{eId} </select>
3.3 返回List类型
上文提到:如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。
-
案例代码
/*** 查询所有员工信息(测试返回多个POJO-List) */ List<Employee> selectAllEmp();
<select id="selectAllEmp" resultType="employee">selecte_id,e_name,salary,address,d_idfromt_emp </select>
3.4 返回Map类型
适用于SQL查询返回的各个字段综合起来并不与任何一个现有的实体类对应,所以不能直接封装到实体类对象中。能够封装成实体类类型的,就不使用Map类型。
在 MyBatis 中,
@MapKey
注解用于指定当查询结果被转换为Map
类型时,用哪个字段作为键。当你希望将查询的结果集映射到一个Map
中,并且这个Map
的键是结果集中某一列的值时,你可以使用@MapKey
注解。具体来说,当你有一个方法返回类型为
Map<K,V>
时,MyBatis 默认会创建一个Map<Integer, V>
,其中键是自动生成的整数索引,而值则是你的对象。如果你想要改变这种行为,让Map
的键成为结果集中某个特定字段的值,你就可以使用@MapKey
来指明这一点。
-
案例代码
/*** 查询所有员工信息(测试返回多个POJO-Map) */ @MapKey("eId") Map<Integer,Employee> selectAllEmpRetrunMap();
<select id="selectAllEmpRetrunMap" resultType="employee">selecte_id,e_name,salary,address,d_idfromt_emp </select>
3.5 返回注解自增数值
3.5.1 返回自增长类型主键
-
Mapper接口中相应方法
int insertEmployee(Employee employee);
-
映射文件中相应SQL
<!-- int insertEmployee(Employee employee); --> <!-- useGeneratedKeys属性字面意思就是“使用生成的主键” --> <!-- keyProperty属性可以指定主键在实体类对象中对应的属性名,Mybatis会将拿到的主键值存入这个属性 --> <insert id="insertEmployee" useGeneratedKeys="true" keyProperty="empId">insert into t_emp(emp_name,emp_salary)values(#{empName},#{empSalary}) </insert>
-
单元测试代码
@Test public void testSaveEmp() {EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);Employee employee = new Employee();employee.setEmpName("john");employee.setEmpSalary(666.66);employeeMapper.insertEmployee(employee);System.out.println("employee.getEmpId() = " + employee.getEmpId()); }
3.5.2 返回非自增长类型主键
对于不支持自增型主键的数据库(例如 Oracle)或者字符串类型主键,则可以使用 selectKey 子元素:selectKey 元素将会首先运行,id 会被设置,然后插入语句会被调用!
-
使用
selectKey
帮助插入UUID作为字符串类型主键示例:<insert id="insertUser" parameterType="User"><selectKey keyProperty="id" resultType="java.lang.String"order="BEFORE">SELECT UUID() as id</selectKey>INSERT INTO user (id, username, password) VALUES (#{id},#{username},#{password}) </insert>
在上例中,我们定义了一个
insertUser
的插入语句来将User
对象插入到user
表中。我们使用selectKey
来查询 UUID 并设置到id
字段中。通过
keyProperty
属性来指定查询到的 UUID 赋值给对象中的id
属性,而resultType
属性指定了 UUID 的类型为java.lang.String
。需要注意的是,我们将
selectKey
放在了插入语句的前面,这是因为 MySQL 在insert
语句中只支持一个select
子句,而selectKey
中查询 UUID 的语句就是一个select
子句,因此我们需要将其放在前面。最后,在将
User
对象插入到user
表中时,我们直接使用对象中的id
属性来插入主键值。使用这种方式,我们可以方便地插入 UUID 作为字符串类型主键。当然,还有其他插入方式可以使用,如使用Java代码生成UUID并在类中显式设置值等。需要根据具体应用场景和需求选择合适的插入方式。
3.6 mapperXML映射文件总结
MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。
SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):
insert
– 映射插入语句。update
– 映射更新语句。delete
– 映射删除语句。select
– 映射查询语句。
select标签:
MyBatis 在查询和结果映射做了相当多的改进。一个简单查询的 select 元素是非常简单:
<select id="selectPerson"
resultType="hashmap"> SELECT * FROM PERSON WHERE ID = #{id} </select>
这个语句名为 selectPerson,接受一个 int(或 Integer)类型的参数,并返回一个 HashMap 类型的对象,其中的键是列名,值便是结果行中的对应值。
注意参数符号:#{id}
MyBatis 创建一个预处理语句(PreparedStatement)参数,在 JDBC 中,这样的一个参数在 SQL 中会由一个“?”来标识,并被传递到一个新的预处理语句中,就像这样:
// 近似的 JDBC 代码,非 MyBatis 代码...
String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);
select 元素允许你配置很多属性来配置每条语句的行为细节:
属性 | 描述 |
---|---|
id | 在命名空间中唯一的标识符,可以被用来引用这条语句。 |
resultType | 期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。 |
resultMap | 对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。 |
timeout | 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。 |
statementType | 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 |
insert, update 和 delete标签:
数据变更语句 insert,update 和 delete 的实现非常接近:
<insertid="insertAuthor"statementType="PREPARED"keyProperty=""keyColumn=""useGeneratedKeys=""timeout="20"><updateid="updateAuthor"statementType="PREPARED"timeout="20"><deleteid="deleteAuthor"statementType="PREPARED"timeout="20">
属性 | 描述 |
---|---|
id | 在命名空间中唯一的标识符,可以被用来引用这条语句。 |
timeout | 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。 |
statementType | 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 |
useGeneratedKeys | (仅适用于 insert 和 update)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。 |
keyProperty | (仅适用于 insert 和 update)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset )。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
keyColumn | (仅适用于 insert 和 update)设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
3.7 CRUD强化练习
-
准备数据库数据
首先,我们需要准备一张名为
user
的表。该表包含字段 id(主键)、username、password。创建SQL如下:CREATE TABLE `user` (`id` INT(11) NOT NULL AUTO_INCREMENT,`username` VARCHAR(50) NOT NULL,`password` VARCHAR(50) NOT NULL,PRIMARY KEY (`id`) ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-
实体类准备
接下来,我们需要定义一个实体类
User
,来对应 user 表的一行数据。@Data public class User {private Integer id;private String username;private String password; }
-
Mapper接口定义
定义一个 Mapper 接口
UserMapper
,并在其中添加 user 表的增、删、改、查方法。public interface UserMapper {int insert(User user);int update(User user);int delete(Integer id);User selectById(Integer id);List<User> selectAll(); }
-
MapperXML编写
在 resources /mappers目录下创建一个名为
UserMapper.xml
的 XML 文件,包含与 Mapper 接口中相同的五个 SQL 语句,并在其中,将查询结果映射到User
实体中。<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""https://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!-- namespace等于mapper接口类的全限定名,这样实现对应 --> <mapper namespace="com.atguigu.mapper.UserMapper"><!-- 定义一个插入语句,并获取主键值 --><insert id="insert" useGeneratedKeys="true" keyProperty="id">INSERT INTO user(username, password)VALUES(#{username}, #{password})</insert><update id="update">UPDATE user SET username=#{username}, password=#{password}WHERE id=#{id}</update><delete id="delete">DELETE FROM user WHERE id=#{id}</delete><!-- resultType使用user别名,稍后需要配置!--><select id="selectById" resultType="user">SELECT id, username, password FROM user WHERE id=#{id}</select><!-- resultType返回值类型为集合,所以只写范型即可! --><select id="selectAll" resultType="user">SELECT id, username, password FROM user</select></mapper>
-
MyBatis配置文件
位置:resources: mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration><settings><!-- 开启驼峰式映射--><setting name="mapUnderscoreToCamelCase" value="true"/><!-- 开启logback日志输出--><setting name="logImpl" value="SLF4J"/></settings><typeAliases><!-- 给实体类起别名 --><package name="com.atguigu.pojo"/></typeAliases><!-- environments表示配置Mybatis的开发环境,可以配置多个环境,在众多具体环境中,使用default属性指定实际运行时使用的环境。default属性的取值是environment标签的id属性的值。 --><environments default="development"><!-- environment表示配置Mybatis的一个具体的环境 --><environment id="development"><!-- Mybatis的内置的事务管理器 --><transactionManager type="JDBC"/><!-- 配置数据源 --><dataSource type="POOLED"><!-- 建立数据库连接的具体信息 --><property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/><property name="username" value="root"/><property name="password" value="root"/></dataSource></environment></environments><mappers><!-- Mapper注册:指定Mybatis映射文件的具体位置 --><!-- mapper标签:配置一个具体的Mapper映射文件 --><!-- resource属性:指定Mapper映射文件的实际存储位置,这里需要使用一个以类路径根目录为基准的相对路径 --><!-- 对Maven工程的目录结构来说,resources目录下的内容会直接放入类路径,所以这里我们可以以resources目录为基准 --><mapper resource="mappers/UserMapper.xml"/></mappers></configuration>
-
效果测试
package com.atguigu.test;import com.atguigu.mapper.UserMapper; import com.atguigu.pojo.User; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test;import java.io.IOException; import java.util.List;/*** projectName: com.atguigu.test*/ public class MyBatisTest {private SqlSession session;// junit会在每一个@Test方法前执行@BeforeEach方法@BeforeEachpublic void init() throws IOException {session = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml")).openSession();}@Testpublic void createTest() {User user = new User();user.setUsername("admin");user.setPassword("123456");UserMapper userMapper = session.getMapper(UserMapper.class);userMapper.insert(user);System.out.println(user);}@Testpublic void updateTest() {UserMapper userMapper = session.getMapper(UserMapper.class);User user = userMapper.selectById(1);user.setUsername("root");user.setPassword("111111");userMapper.update(user);user = userMapper.selectById(1);System.out.println(user);}@Testpublic void deleteTest() {UserMapper userMapper = session.getMapper(UserMapper.class);userMapper.delete(1);User user = userMapper.selectById(1);System.out.println("user = " + user);}@Testpublic void selectByIdTest() {UserMapper userMapper = session.getMapper(UserMapper.class);User user = userMapper.selectById(1);System.out.println("user = " + user);}@Testpublic void selectAllTest() {UserMapper userMapper = session.getMapper(UserMapper.class);List<User> userList = userMapper.selectAll();System.out.println("userList = " + userList);}// junit会在每一个@Test方法后执行@@AfterEach方法@AfterEachpublic void clear() {session.commit();session.close();} }
第4章 resultType与resultMap
4.1 多表&实体类映射关系
4.1.1 表与表之间的映射关系
-
一对一
夫妻关系,人和身份证号
-
一对多
部门对员工,用户和用户的订单,锁和钥匙
-
多对多
老师和学生,公交车与路人
-
案例代码
-
4.1.2 实体类的映射关系
-
对一
-
用户订单对用户
public class Customer {private Integer customerId;private String customerName;}public class Order {private Integer orderId;private String orderName;private Customer customer;// 体现的是对一的关系}
-
员工对部门
package com.atguigu.pojo;import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;@Data @NoArgsConstructor @AllArgsConstructor public class Employee {private Integer eId;private String eName;private Integer salary;private String address;//private Integer dId;private Department dept;}
-
-
对多
-
用户对用户订单
public class Customer {private Integer customerId;private String customerName;private List<Order> orderList;// 体现的是对多的关系 }
-
部门对员工
package com.atguigu.pojo;import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;import java.util.List;@Data @NoArgsConstructor @AllArgsConstructor public class Department {private Integer dId;private String dName;private String description;//对应员工信息private List<Employee> employeeList;}
-
4.2 resultType与resultMap应用场景
在 MyBatis 中,
resultType
和resultMap
是两种不同的方式,用于描述如何将 SQL 查询结果映射到 Java 对象。它们有各自的特点和使用场景。具体区别如下:
resultType
- 定义:
resultType
用于指定查询结果应映射到的 Java 类型。它可以是基本类型、JavaBean、POJO 或 Map 等。- 特点
- 简单直接,适合于简单的映射关系,即 SQL 查询的结果集列名与 Java 对象的属性名一一对应。
- 不需要额外的配置,只需要提供目标类型的全限定类名或别名。
- 适用场景
- 当查询语句返回的字段可以直接映射到 Java 类的属性时,且不需要处理复杂的映射关系(如嵌套对象、多对一、一对多等)。
- 所以resultType一般用于单表简单查询
resultMap
- 定义:
resultMap
是 MyBatis 中最强大和灵活的映射机制。它允许你详细地定义如何从数据库结果集中映射数据到应用程序中的对象。- 特点
- 提供了更精细的控制,可以处理复杂的数据结构映射,比如关联映射(一对一、一对多)、嵌套结果、鉴别器等。
- 需要在 XML 映射文件中定义
<resultMap>
元素,并为每个复杂的映射规则指定具体的映射逻辑。- 适用场景
- 当存在复杂的映射需求时,例如表结构和对象模型不一致;或者需要进行联合查询并将结果映射到多个对象;或者是处理继承结构等情况。
- 所以resultMap一般用于多表复杂查询
4.3 实现对一映射
4.3.1 级联方式实现对一映射
-
需求说明及POJO准备
查询员工信息及员工所属部门信息(部门id部门名称)
-
POJO准备
package com.atguigu.pojo;import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;@Data @NoArgsConstructor @AllArgsConstructor public class Employee {private Integer eId;private String eName;private Integer salary;private String address;//private Integer dId;private Department dept;}
-
EmployeeMapper接口
/***/ @Mapper public interface EmployeeMapper {/*** 查询员工信息及员工所属部门信息(部门id部门名称)*/List<Employee> selectAllEmpAndDept();}
-
EmployeeMapper.xml映射文件
<resultMap id="empAndDeptResultMap" type="employee"><!--定义员工表中字段与员工实体类属性的映射关系--><id column="e_id" property="eId"/><result column="e_name" property="eName"/><result column="salary" property="salary"/><result column="address" property="address"/><!--定义部门表中字段与部门实体类属性的映射关系--><result column="did" property="dept.dId" /><result column="dname" property="dept.dName"/><result column="description" property="dept.description"/> </resultMap><select id="selectAllEmpAndDept" resultMap="empAndDeptResultMap">selecte.e_id,e.e_name,e.salary,e.address,d.did,d.dname,d.descriptionfromt_emp e left join t_dept done.d_id=d.did </select>
-
测试
@SpringBootTest(classes = Day19Mybatis1Application.class) class Day19Mybatis1ApplicationTests {@Autowiredprivate EmployeeMapper employeeMapper;/*** 测试对一映射*/@Testvoid contextLoads() {//级联映射employeeMapper.selectAllEmpAndDept().forEach(System.out::println);} }
4.3.2 association实现对一映射
-
需求说明及POJO准备
查询员工信息及员工所属部门信息(部门id部门名称)association
POJO同上
-
EmployeeMapper接口
/*** 查询员工信息及员工所属部门信息(部门id部门名称)association */ List<Employee> selectAllEmpAndDeptAssociation();
-
EmployeeMapper.xml映射文件
<resultMap id="empAndDeptAssociation" type="employee"><!--定义员工表中字段与员工实体类属性的映射关系--><id column="e_id" property="eId"/><result column="e_name" property="eName"/><result column="salary" property="salary"/><result column="address" property="address"/><!--association(对一)定义部门表中字段与部门实体类属性的映射关系--><association property="dept" javaType="department"><id column="did" property="dId"/><result column="dname" property="dName"/><result column="description" property="description"/></association> </resultMap> <select id="selectAllEmpAndDeptAssociation" resultMap="empAndDeptAssociation">selecte.e_id,e.e_name,e.salary,e.address,d.did,d.dname,d.descriptionfromt_emp e left join t_dept done.d_id=d.did </select>
-
测试(略)
4.4 实现对多映射
-
需求说明POJO准备
通过部门id获取部门信息及部门对应员工信息
package com.atguigu.pojo;import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;import java.util.List;@Data @NoArgsConstructor @AllArgsConstructor public class Department {private Integer dId;private String dName;private String description;//对应员工信息private List<Employee> employeeList;}
-
DepartmentMapper接口
/***/ @Mapper public interface DepartmentMapper {/*** 通过部门id获取部门信息及部门对应员工信息*/Department selectDeptAndEmpByDeptId(Integer deptId);}
-
DepartmentMapper.xml映射文件
<resultMap id="deptAndEmpResultMap" type="department"><id column="did" property="dId"/><result column="dname" property="dName"/><collection property="employeeList" ofType="employee"><id column="e_id" property="eId"/><result column="e_name" property="eName"/><result column="salary" property="salary"/><result column="address" property="address"/></collection> </resultMap> <select id="selectDeptAndEmpByDeptId" resultMap="deptAndEmpResultMap">SELECTd.did,d.dname,e.e_id,e.e_name,e.salary,e.addressFROMt_dept dLEFT JOINt_emp eONd.did = e.d_idWHEREd.did = #{deptId} </select>
-
测试
/*** 测试对多映射 */ @Test void testToMore() {Department department = departmentMapper.selectDeptAndEmpByDeptId(1);System.out.println("department = " + department); }
4.5 resultMap小结
关联关系 | 配置项关键词 | 所在配置文件和具体位置 |
---|---|---|
对一 | association标签/javaType属性 | Mapper配置文件中的resultMap标签内 |
对多 | collection标签/ofType属性 | Mapper配置文件中的resultMap标签内 |
对一分步 | association标签/select属性/column属性 | Mapper配置文件中的resultMap标签内 |
对多分步 | collection标签/select属性/column属性 | Mapper配置文件中的resultMap标签内 |
延迟加载 3.4.1版本前 | lazyLoadingEnabled设置为true aggressiveLazyLoading设置为false | Mybatis全局配置文件中的settings标签内 |
延迟加载 3.4.1版本后 | lazyLoadingEnabled设置为true | Mybatis全局配置文件中的settings标签内 |
resultMap属性
- id:定义当前resultMap的唯一标识
- type:定义[目标方法的返回值]类型(全类名|别名)
resultMap子标签
- :定义主键字段与类中属性的映射关系
- :定义非主键字段与类中属性的映射关系
- column:定义表中的字段名称
- property:定义类中的属性名称
- :定义(对一)表中的字段与类中的属性映射关系
- property属性:定义对一属性名称
- javaType属性:定义对一属性类型全类名|别名)
- :定义(对多)表中的字段与类中的属性映射关系
- property属性:定义对多属性名称
- ofType属性:定义对多属性类型全类名|别名)
设置自动映射
#设置自动映射full(selectAllEmpAndDeptAssociation) mybatis.configuration.auto-mapping-behavior=full
4.6 实现分步查询
4.6.1 分步查询概念
在 MyBatis 中,分步查询(Step-by-step Query 或 Stepwise Query)通常与关联对象的懒加载(Lazy Loading)或延迟加载共同使用。这种机制允许你在获取主对象时并不立即加载与之关联的对象,而是当真正需要访问这些关联对象的数据时才进行加载。这可以显著提高性能,尤其是在处理大型数据集或复杂对象图时。
分步查询的意义
- 性能优化:并非所有情况下都需要加载所有的关联对象。例如,在一个用户-订单系统中,你可能只需要展示用户的某些基本信息,而不需要立刻加载该用户的所有订单信息。通过分步查询,你可以避免不必要的数据库查询,从而节省资源和时间。
- 减少内存占用:对于大规模的数据集合,一次性加载所有相关联的数据可能会导致大量的内存消耗。分步查询可以让应用程序只在需要的时候才加载特定的数据,从而减少了内存的使用。
- 按需加载:能够根据业务逻辑的需求来决定何时以及如何加载关联对象,提供更大的灵活性。
分步查询的本质
- 将复杂的多表连接查询,改为单表的分步查询
分步查询的实现方式
- 在service层实现:在service中分步调用mapper接口即可
- 在mapper层实现:在mapper层实现分步查询,可以降低service层代码复杂度
4.6.2 对一分步查询
-
需求说明
通过员工eid获取员工信息及员工的部门信息(分步查询实现)
- 通过eid 查询员工信息(d_id)
- 通过d_id 查询部门信息
-
相关代码
-
EmployeeMapper
/*** 通过员工id获取员工信息及员工的部门信息(测试分步查询)可能效率:select * from t_emp e left join t_dept d on e.dept_id = d.id where e.id = #{eId}提高效率1. 通过eid 查询员工信息(d_id)2. 通过d_id 查询部门信息 */ Employee selectEmpAndDeptByeIdStep(Integer eId);
-
EmployeeMapper.xml
<!-- 分步查询1. 通过eid 查询员工信息(d_id)2. 通过d_id 查询部门信息 --> <resultMap id="empAndDempStepResultMap" type="employee"><!--定义员工表中字段与员工实体类属性的映射关系--><id column="e_id" property="eId"/><result column="e_name" property="eName"/><result column="salary" property="salary"/><result column="address" property="address"/><!--分步查询--><association property="dept"javaType="department"select="com.atguigu.mapper.DepartmentMapper.selectDeptByDeptId"column="d_id"/> </resultMap> <select id="selectEmpAndDeptByeIdStep" resultMap="empAndDempStepResultMap">selecte_id,e_name,salary,address,d_idfromt_empwheree_id = #{eId} </select>
-
DepartmentMapper
/*** 通过部门id获取部门信息 */ Department selectDeptByDeptId(Integer deptId);
-
DepartmentMapper.xml
<select id="selectDeptByDeptId" resultType="department">SELECTdid,dnameFROMt_deptWHEREdid = #{deptId} </select>
-
关联关系
-
4.6.3 对多分步查询
-
需求说明
通过部门id获取部门信息,及部门对应的员工信息(分步实现)
- 通过部门id查询部门信息
- 通过部门id查询部门对应的员工信息
-
相关代码
-
DepartmentMapper
/*** 对多分步查询* 通过部门id获取部门信息,及部门对应的员工信息* 1. 通过部门id查询部门信息* 2. 通过部门id查询部门对应的员工信息 */ Department selectDeptAndEmpByDeptIdStep(Integer eId);
-
DepartmentMapper.xml
<!-- 测试分步查询(对多)1. 通过部门id查询部门信息2. 通过部门id查询部门对应的员工信息 --><resultMap id="deptAndEmpStepRM" type="department"><id column="did" property="dId"/><result column="dname" property="dName"/><!-- 定义对多的分步查询--><collection property="employeeList"ofType="employee"select="com.atguigu.mapper.EmployeeMapper.selectEmpByDeptId"column="did"></collection></resultMap><select id="selectDeptAndEmpByDeptIdStep" resultMap="deptAndEmpStepRM">SELECTdid,dnameFROMt_deptWHEREdid = #{deptId}</select>
-
EmployeeMapper
/*** 通过部门id获取员工信息 */ List<Employee> selectEmpByDeptId(int deptId);
-
EmployeeMapper.xml
<select id="selectEmpByDeptId" resultType="employee">selecte_id,e_name,salary,address,d_idfromt_empwhered_id = #{deptId} </select>
-
4.7 实现延迟加载(懒加载)
4.7.1 开启延迟加载
-
配置文件application.properties或application.yml
#开启延迟加载(懒加载) mybatis.configuration.lazy-loading-enabled=true mybatis.configuration.aggressive-lazy-loading=false
4.7.2 测试延迟加载
@Test
void testMoreStepSelect() {//测试对一分步查询Employee employee = employeeMapper.selectEmpAndDeptByeIdStep(2);String eName = employee.getEName();System.out.println("eName = " + eName);System.out.println("==================");System.out.println(" dept:"+ employee.getDept());}
第5章 Mybatis动态SQL
5.1 Mybatis动态SQL概述
MyBatis 的动态 SQL 是指在编写 SQL 语句时,可以根据不同的条件动态地构建 SQL 语句。这使得你可以创建灵活且可重用的查询逻辑,而不需要为每个可能的查询条件都写一个独立的 SQL 语句。MyBatis 提供了多种标签来实现这一功能,包括
<if>
、<choose>
(类似于 Java 的switch
)、<when>
、<otherwise>
、<trim>
、<where>
、<set>
、<foreach>
等。Mybatis动态SQL中应用到OGNL语言
OGNL(Object-Graph Navigation Language)是一种强大的表达式语言,最初用于 WebWork 和 XWork 框架中,后来被 Apache Struts2 广泛采用。MyBatis 也使用 OGNL 来处理动态 SQL 中的条件判断和属性访问。OGNL 允许你通过简洁的语法访问和操作 Java 对象图中的数据。
表达式语法
OGNL 的表达式可以用来访问对象的属性、调用方法、获取数组或列表的元素等。它的基本语法非常直观:
- 访问属性:
person.name
- 调用方法:
person.getName()
- 访问数组/列表元素:
list[0]
- 访问 Map 键值:
map['key']
运算符
OGNL 支持多种运算符,包括但不限于:
- 算术运算符:
+
,-
,*
,/
,%
- 关系运算符:
==
,<
,>
,<=
,>=
,!=
- 逻辑运算符:
&&
,||
,!
- 三元运算符:
condition ? trueValue : falseValue
5.2 Mybatis动态SQL常用标签
5.2.1 <if>标签
-
作用
<if>
标签用于根据条件包含或排除 SQL 片段。它有一个test
属性,该属性是一个 OGNL 表达式,用来判断是否满足某个条件。 -
案例
/*** 按条件查询员工信息(动态条件) */ List<Employee> selectEmpByDynamicCondition(Employee employee);
<!-- 动态SQL--> <select id="selectEmpByDynamicCondition" resultType="employee">selecte_id,e_name,salary,addressfromt_empwhere 1= 1<if test="eId != null">and e_id = #{eId}</if><if test="eName != null">and e_name like concat('%',#{eName},'%')</if> </select>
5.2.2 <where>标签
-
作用
<where>
标签简化了条件语句的处理,它会自动处理第一个AND
或OR
,并且当没有条件时不会生成WHERE
子句。 -
案例
/*** 按条件查询员工信息(动态条件) */ List<Employee> selectEmpByDynamicConditionWhere(Employee employee);
<!-- 动态SQL--> <select id="selectEmpByDynamicConditionWhere" resultType="employee">selecte_id,e_name,salary,addressfromt_emp<where><if test="eId != null">e_id = #{eId}</if><if test="eName != null">and e_name like concat('%',#{eName},'%')</if></where> </select>
5.2.3 <choose>标签
-
作用
<choose>
,<when>
,<otherwise>
这些标签提供了类似于 Java 中
switch
语句的功能,用于多条件分支选择。 -
案例
/*** 按条件查询员工信息(动态条件) */ List<Employee> selectEmpByChoose(Employee employee);
<select id="selectEmpByChoose" resultType="employee">selecte_id,e_name,salary,addressfromt_emp<where><choose><when test="eId != null">e_id = #{eId}</when><when test="eName != null">e_name = #{eName}</when><when test="salary != null">salary = #{salary}</when><when test="address != null">address = #{address}</when><otherwise>1=1</otherwise></choose></where> </select>
5.2.4 <trim>标签
-
作用
<trim>
标签用于去掉多余的前缀或后缀。它的属性有- prefix属性:指定要动态添加的前缀
- suffix属性:指定要动态添加的后缀
- prefixOverrides属性:指定要动态去掉的前缀,使用“|”分隔有可能的多个值
- suffixOverrides属性:指定要动态去掉的后缀,使用“|”分隔有可能的多个值
-
案例
/*** 按条件查询员工信息(动态条件) */ List<Employee> selectEmpByTrim(Employee employee);
<select id="selectEmpByTrim" resultType="employee">selecte_id,e_name,salary,addressfromt_emp<trim prefix="where" suffixOverrides="and|or"><if test="eId != null">e_id = #{eId} and</if><if test="eName != null">e_name like concat('%',#{eName},'%')</if></trim> </select>
5.2.5 <set>标签
-
作用
<set>
标签用于更新语句中,它会自动处理逗号分隔符,并且当没有要更新的内容时不会生成SET
关键字。 -
案例
/*** 动态条件修改员工信息 */ void updateEmpByDynamicCondition(Employee employee);
<update id="updateEmpByDynamicCondition">update t_emp<set><if test="eName != null">e_name = #{eName},</if><if test="salary != null">salary = #{salary},</if><if test="address != null">address = #{address}</if></set><where><if test="eId != null">e_id = #{eId}</if></where> </update>
5.2.6 <foreach>标签
-
作用
<foreach>
标签允许你遍历集合,常用于IN
语句等场景。它有四个重要的属性:collection
(集合名称)、item
(集合中的元素)、open
(开始符号)、close
(结束符号)和separator
(分隔符)。 -
案例
/*** 添加员工信息(测试foreach)* List入参注意:map的key[list|collection] */ void insertEmpByBatch(@Param("empList") List<Employee> employees);
<insert id="insertEmpByBatch">insert into t_emp(e_name,salary,address,d_id) values<foreach collection="empList" item="emp" separator=",">(#{emp.eName},#{emp.salary},#{emp.address},#{emp.dept.dId})</foreach> </insert>
5.2.7 <sql>标签
-
作用
<sql>
标签允许你定义可重用的 SQL 片段,这些片段可以在多个查询中使用。通过这种方式,你可以避免重复代码,并且使得 SQL 语句更易于管理和维护。 -
案例
<sql id="select_emp_all_filed">selecte_id,e_name,salary,addressfromt_emp </sql> <sql id="emp_all_filed">e_id,e_name,salary,address </sql><!-- 动态SQL--> <select id="selectEmpByDynamicCondition" resultType="employee">select<include refid="emp_all_filed"/>fromt_emp<where><if test="eId != null">e_id = #{eId}</if><if test="eName != null">and e_name like concat('%',#{eName},'%')</if></where> </select>
5.2.8 <bind>标签
-
作用
<bind>
标签用于在 SQL 语句中创建变量绑定。它特别适用于需要对某些表达式的结果进行多次引用的情况,或者当你想简化复杂的 OGNL 表达式时。 -
案例
<!-- 动态SQL--> <select id="selectEmpByDynamicCondition" resultType="employee"><bind name="patern" value="'%' + eName + '%'"></bind>selecte_id,e_name,salary,addressfromt_emp<where><if test="eId != null">e_id = #{eId}</if><if test="eName != null">and e_name like #{patern}</if></where> </select>
第6章 Mybatis分页插件
6.1 分页概念
6.1.1 为什么需要分页
分页(Pagination)是处理和展示大量数据时的一种常见技术,它将数据分割成多个页面,每次只显示其中一部分。在软件开发尤其是Web应用中,分页具有多方面的重要性:
- 提升用户体验
更快的响应时间:一次性加载所有数据可能会导致页面加载速度变慢,影响用户体验。通过分页,可以减少每次请求的数据量,从而加快页面响应速度。
更好的可读性:过多的信息同时出现在一个页面上会使用户感到困惑,难以快速找到所需信息。分页可以帮助组织内容,让用户更容易浏览和查找。
- 优化性能
- 减轻服务器负担:对于大型数据集,一次性查询并返回所有记录会给数据库服务器带来巨大压力。分页可以让每次查询只获取必要的数据,降低对服务器资源的需求。
- 减少带宽消耗:传输大量数据会占用较多网络带宽,尤其是在移动网络环境下,这不仅增加了成本还可能导致延迟。分页可以有效减少不必要的数据传输。
- 提高系统的可扩展性和稳定性
- 防止内存溢出:当应用程序需要处理非常大的数据集时,如果不使用分页机制,可能会因为内存不足而导致程序崩溃或异常终止。分页确保了每次只处理有限数量的数据,避免了这种情况的发生。
- 支持大数据处理:随着业务的增长,数据量也会随之增加。良好的分页设计有助于应对不断增长的数据规模,保证系统能够稳定运行。
6.1.2 如何实现分页
-
如没有分页插件,需要自定义分页工具类
自定义Page分页插件
- 当前页:PageNum
- 总页数:Pages
- 总信息数量:Total
- 每页显示数量:PageSize
- 当前页显示数据集合:List
Mybatis中支持分页插件:PageHelper
6.2 分页插件PageHelper应用
6.2.1 添加插件
-
导入依赖
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper --> <dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>6.1.0</version> </dependency>
-
装配PageInterceptor
package com.atguigu.config;import com.github.pagehelper.PageInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;/*** @Author zhangchunsheng* @CreateTime: 2024/11/25*/ @Configuration public class MybatisConfig {/*** @Author: zhangchunsheng* @Date: 2024/11/25 9:15* 将PageInterceptor注入到springIOC容器中,* id: pageInterceptor*/@Bean("pageInterceptor")public PageInterceptor getPageInterceptor() {PageInterceptor pageInterceptor = new PageInterceptor();return pageInterceptor;} }
6.2.2 使用插件
查询之前开启分页:PageHelper.startPage(1,5);
查询之后将查询结果封装到PageInfo中
- PageInfo pageInfo = new PageInfo<>(empList,5);
-
案例代码
@Testpublic void testPageHelper() {//开启分页PageHelper.startPage(2,3);//查询数据List<Employee> employees = employee0XMLMapper.selectAllEmps();//将查询结果封装PageInfo<T>对象//10:导航页PageInfo<Employee> pageInfo = new PageInfo<>(employees,10);System.out.println(pageInfo.getPageNum()+"/"+pageInfo.getPages());List<Employee> empList = pageInfo.getList();empList.forEach(System.out::println);}
@Test public void testTeacherRelationshipToMulti() {TeacherMapper teacherMapper = session.getMapper(TeacherMapper.class);PageHelper.startPage(1,2);// 查询Customer对象同时将关联的Order集合查询出来List<Teacher> allTeachers = teacherMapper.findAllTeachers(); //PageInfo<Teacher> pageInfo = new PageInfo<>(allTeachers);System.out.println("pageInfo = " + pageInfo);long total = pageInfo.getTotal(); // 获取总记录数System.out.println("total = " + total);int pages = pageInfo.getPages(); // 获取总页数System.out.println("pages = " + pages);int pageNum = pageInfo.getPageNum(); // 获取当前页码System.out.println("pageNum = " + pageNum);int pageSize = pageInfo.getPageSize(); // 获取每页显示记录数System.out.println("pageSize = " + pageSize);List<Teacher> teachers = pageInfo.getList(); //获取查询页的数据集合System.out.println("teachers = " + teachers);teachers.forEach(System.out::println); }
第7章 MybatisX之逆向工程
7.1 ORM思维介绍
ORM思维介绍
ORM(Object-Relational Mapping,对象-关系映射)是一种将数据库和面向对象编程语言中的对象之间进行转换的技术。它将对象和关系数据库的概念进行映射,通过一系列的操作将对象关联到数据表中的一行或多行上。
让我们可以使用面向对象思维进行数据库操作!!!
ORM 框架通常有半自动和全自动两种方式。
- 半自动 ORM 通常需要程序员手动编写 SQL 语句或者配置文件,将实体类和数据表进行映射,还需要手动将查询的结果集转换成实体对象。
- 全自动 ORM 则是将实体类和数据表进行自动映射,使用 API 进行数据库操作时,ORM 框架会自动执行 SQL 语句并将查询结果转换成实体对象,程序员无需再手动编写 SQL 语句和转换代码。
下面是半自动和全自动 ORM 框架的区别:
- 映射方式:半自动 ORM 框架需要程序员手动指定实体类和数据表之间的映射关系,通常使用 XML 文件或注解方式来指定;全自动 ORM 框架则可以自动进行实体类和数据表的映射,无需手动干预。
- 查询方式:半自动 ORM 框架通常需要程序员手动编写 SQL 语句并将查询结果集转换成实体对象;全自动 ORM 框架可以自动组装 SQL 语句、执行查询操作,并将查询结果转换成实体对象。
- 性能:由于半自动 ORM 框架需要手动编写 SQL 语句,因此程序员必须对 SQL 语句和数据库的底层知识有一定的了解,才能编写高效的 SQL 语句;而全自动 ORM 框架通过自动优化生成的 SQL 语句来提高性能,程序员无需进行优化。
- 学习成本:半自动 ORM 框架需要程序员手动编写 SQL 语句和映射配置,要求程序员具备较高的数据库和 SQL 知识;全自动 ORM 框架可以自动生成 SQL 语句和映射配置,程序员无需了解过多的数据库和 SQL 知识。
常见的半自动 ORM 框架包括 MyBatis 等;常见的全自动 ORM 框架包括 Hibernate、Spring Data JPA、MyBatis-Plus 等。
7.2 逆向工程插件-MybatisX
7.2.1 逆向工程基本概念
MyBatis 的逆向工程是一种自动化生成持久层代码和映射文件的工具,它可以根据数据库表结构和设置的参数生成对应的实体类、Mapper.xml 文件、Mapper 接口等代码文件,简化了开发者手动生成的过程。逆向工程使开发者可以快速地构建起 DAO 层,并快速上手进行业务开发。
MyBatis 的逆向工程有两种方式:通过 MyBatis Generator 插件实现和通过 Maven 插件实现。无论是哪种方式,逆向工程一般需要指定一些配置参数,例如数据库连接 URL、用户名、密码、要生成的表名、生成的文件路径等等。
总的来说,MyBatis 的逆向工程为程序员提供了一种方便快捷的方式,能够快速地生成持久层代码和映射文件,是半自动 ORM 思维像全自动发展的过程,提高程序员的开发效率。注意:逆向工程只能生成单表crud的操作,多表查询依然需要我们自己编写!
相关文章:
MyBatis持久层框架
第1章 Mybatis框架入门 1.1 Mybatis简介 MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下, iBatis3.x正式更名为MyBatis。代码于2013年11月迁移到Github。 MyBati…...
DeePseek结合PS!批量处理图片的方法教程
今天我们来聊聊如何利用deepseek和Photoshop(PS)实现图片的批量处理。 传统上,批量修改图片尺寸、分辨率等任务往往需要编写脚本或手动处理,而现在有了AI的辅助,我们可以轻松生成PS脚本,实现自动化处…...
【C++】多态(下)
大家好,我是苏貝,本篇博客带大家了解C的多态,如果你觉得我写的还不错的话,可以给我一个赞👍吗,感谢❤️ 目录 4. 多态的原理4.1 虚函数表4.2 多态的原理4.3 动态绑定与静态绑定 5. 单继承和多继承关系的虚…...
C++ 入门速通-第4章【黑马】
内容来源于:黑马 集成开发环境:CLion 先前学习完了C第1章的内容: C 入门速通-第1章【黑马】-CSDN博客 C 入门速通-第2章【黑马】-CSDN博客 C 入门速通-第3章【黑马】-CSDN博客 下面继续学习第4章: 结构体的基本应用࿱…...
Gauss高斯:分布键
分布键是决定数据分布到不同节点的列,直接影响数据的存储位置和后续查询的数据流向. 在分布式数据库系统中,分布键用于决定数据如何在不同的节点或分区中分布。 作用 分类 分布键的选择 避免常量过滤条件的字段:在选择分布键时,…...
Spring Security(maven项目) 3.0.3.1版本 - 动态JDBC认证
前言: 通过实践而发现真理,又通过实践而证实真理和发展真理。从感性认识而能动地发展到理性认识,又从理性认识而能动地指导革命实践,改造主观世界和客观世界。实践、认识、再实践、再认识,这种形式,循环往…...
2024年半导体行业IPO与融资情况统计分析
重点内容速览: 1. IPO企业主要集中在科创板 2. 全年半导体行业融资事件超700起 2024年半导体行业的IPO和融资情况呈现出了显著的波动和变化。由于IPO政策的收紧,2024年成功上市的企业相比2023年的22家和2022年的45家有了显著降低,今年仅有1…...
Java 进阶 01 —— 5 分钟回顾一下 Java 基础知识
Java 进阶 01 —— 5 分钟回顾一下 Java 基础知识 Java 生态圈Java 跨平台的语言 Java 虚拟机规范JVM 跨语言的平台多语言混合编程两种架构 举例 JVM 的生命周期 虚拟机的启动虚拟机的执行虚拟机的退出 JVM 发展历程 Sun Classic VMExact VMHotSpotBEA 的 JRockitIBM 的 J9 …...
java进阶文章链接
java 泛型:java 泛型详解-绝对是对泛型方法讲解最详细的,没有之一 Java 泛型,你了解类型擦除吗? java 注解:深入理解Java注解类型 秒懂,Java 注解 (Annotation)你可以这样学 jav…...
【从零开始入门unity游戏开发之——C#篇48】C#补充知识点——静态导入、异常捕获和异常筛选器、nameof运算符
考虑到每个人基础可能不一样,且并不是所有人都有同时做2D、3D开发的需求,所以我把 【零基础入门unity游戏开发】 分为成了C#篇、unity通用篇、unity3D篇、unity2D篇。 【C#篇】:主要讲解C#的基础语法,包括变量、数据类型、运算符、流程控制、面向对象等,适合没有编程基础的…...
离散浣熊优化算法(DCOA)求解大规模旅行商问题(Large-Scale Traveling Salesman Problem,LTSP),MATLAB代码
大规模旅行商问题(Large-Scale Traveling Salesman Problem,LTSP)是经典旅行商问题(TSP)在规模上的扩展,是一个具有重要理论和实际意义的组合优化问题: 一、问题定义 给定一组城市和它们之间的…...
联邦学习中的一些专业术语
1. **Model Mixture(模型混合)** - **含义**:在联邦学习中,不同客户端的模型可能会被混合或融合,以生成一个更通用的全局模型。这种技术可以用于处理客户端之间的异质性。 - **应用场景**:当不同客户…...
从零开始实现一个双向循环链表:C语言实战
文章目录 1链表的再次介绍2为什么选择双向循环链表?3代码实现:从初始化到销毁1. 定义链表节点2. 初始化链表3. 插入和删除节点4. 链表的其他操作5. 打印链表和判断链表是否为空6. 销毁链表 4测试代码5链表种类介绍6链表与顺序表的区别7存储金字塔L0: 寄存…...
4种架构的定义和关联
文章目录 **1. 各架构的定义****业务架构(Business Architecture)****应用架构(Application Architecture)****数据架构(Data Architecture)****技术架构(Technology Architecture)*…...
一文解释pytorch 中的 squeeze() 和 unsqueeze()函数(全网最详细版)
🌈 个人主页:十二月的猫-CSDN博客 🔥 系列专栏: 🏀零基础入门PyTorch框架_十二月的猫的博客-CSDN博客 💪🏻 十二月的寒冬阻挡不了春天的脚步,十二点的黑夜遮蔽不住黎明的曙光 目录 …...
4. k8s二进制集群之ETCD集群证书生成
安装cfssl工具配置CA证书请求文件创建CA证书创建CA证书策略配置etcd证书请求文件生成etcd证书 继续上一篇文章《负载均衡器高可用部署》下面介绍一下etcd证书生成配置。其中涉及到的ip地址和证书基本信息请替换成你自己的信息。 安装cfssl工具 下载cfssl安装包 https://github…...
验证工具:VCS概识
是的,我熟悉 VCS(Synopsys Verilog Compiler Simulator),它是芯片验证领域广泛使用的商用仿真工具之一,由Synopsys公司开发。以下是对VCS的详细介绍,包括其特点、应用场景和常见使用技巧: 一、VCS常见介绍 1. VCS的核心特点 编译型仿真器:VCS将RTL(Verilog/SystemVer…...
Linux 的 sysfs 伪文件系统介绍【用户可以通过文件操作与内核交互(如调用内核函数),而无需编写内核代码】
1. 什么是 sysfs伪文件系统? sysfs 是 Linux 内核提供的 伪文件系统,用于向用户空间暴露内核对象的信息和控制接口。它是 procfs 的补充,主要用于管理 设备、驱动、内核子系统 等信息,使用户可以通过文件操作(如用户空…...
4.[ISITDTU 2019]EasyPHP
进入题目页面如下 给出PHP源码进行代码审计 <?php // 高亮显示当前文件的源代码 highlight_file(__FILE__);// 从 GET 请求中获取名为 _ 的参数,并将其赋值给变量 $_ // 符号用于抑制可能出现的错误信息 $_ $_GET[_];// 使用正则表达式对 $_ 变量的值进行匹配…...
【数学】矩阵、向量(内含矩阵乘法C++)
目录 一、前置知识:向量(一列或一行的矩阵)、矩阵1. 行向量2. 列向量3. 向量其余基本概念4. 矩阵基本概念5. 关于它们的细节 二、运算1. 转置(1)定义(2)性质 2. 矩阵(向量࿰…...
Centos 8 离线升级openssh 9.9
背景 根据云服务漏检报告,需要升级云服务器openssh服务(离线环境)。本文将采用rpm包形式,将openssh服务由OpenSSH_8.0p1 升级至OpenSSH_9.9p1。准备一台能够联网的服务器(简称server1)用于下载程序包&#…...
Java入门进阶
文章目录 1、常用API 1.1、Math1.2、System1.3、Object1.4、Arrays1.5、基本类型包装类 1.5.1、基本类型包装类概述1.5.2、Integer1.5.3、int和String相互转换1.5.4、自动装箱和拆箱 1.6、日期类 1.6.1、Date类1.6.2、SimpleDateFormat类 1.6.2.1、格式化(从Date到…...
torchtext.get_tokenizer
文章目录 1. 说明2. pytorch代码 1. 说明 假设我们有一个句子如下:You can now install TorchText using pip! 分词后可得:[you, can, now, install, torchtext, using, pip, !] 2. pytorch代码 import torchtext from torchtext.data import get_tok…...
零技术开始,但想用 Next.js 基于 React 构建一个类似 18Touch 的网站
如果你从零技术开始,但想用 Next.js 基于 React 构建一个类似 18Touch 的网站,以下是一个详细的规划和学习路径。这个网站看起来是一个内容展示型网站,可能包含博客、产品展示、用户交互等功能。 1. 项目规划 1.1 确定核心功能 根据 18Touc…...
python实现多路视频,多窗口播放功能
系列Python开发 文章目录 系列Python开发前言一、python实现多路视频播放功能二、代码实现1. http申请视频流地址并cv2播放功能 三、打包代码实现生成可执行文件 总结 前言 一、python实现多路视频播放功能 服务端开发后通常需要做功能测试、性能测试,通常postman、…...
设计模式---观察者模式
设计模式—观察者模式 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。 主要解决的问题:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,…...
TensorFlow深度学习实战(6)——回归分析详解
TensorFlow深度学习实战(6)——回归分析详解 0. 前言1. 回归分析简介2. 线性回归2.1 简单线性回归2.2 多重线性回归2.3 多元线性回归 3. 构建基于线性回归的神经网络3.1 使用 TensorFlow 进行简单线性回归3.2 使用 TensorFlow 进行多元线性回归和多重线性…...
Deepseek v3R1 学习笔记
o1 o1 模型在训练过程中混合了多种奖励函数的设计方法,并且尝试从结果监督转向过程监督,在中间过程进行打分 使用的搜索策略:基于树的搜索和基于顺序修改的搜索 R1 R1-Zero 是从基础模型开始,完全由强化学习驱动,不…...
Spring Boot统一异常拦截实践指南
Spring Boot统一异常拦截实践指南 一、为什么需要统一异常处理 在Web应用开发中,异常处理是保证系统健壮性和用户体验的重要环节。传统开发模式中常见的痛点包括: 异常处理逻辑分散在各个Controller中错误响应格式不统一敏感异常信息直接暴露给客户端…...
lmk内存压力测试工具mem-pressure源码剖析
背景: android系统开发过程中,经常会遇到一些low memory kill的问题,在分析这些系统低内存导致被杀问题时候,经常因为不好复现而成为一个比较烦恼的阻碍。因为这种低内存问题本身就不属于一种功能操作类型的问题,属于…...
物联网 STM32【源代码形式-ESP8266透传】连接OneNet IOT从云产品开发到底层MQTT实现,APP控制 【保姆级零基础搭建】
一、MQTT介绍 MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议)是一种基于发布/订阅模式的轻量级通讯协议,构建于TCP/IP协议之上。它最初由IBM在1999年发布,主要用于在硬件性能受限和网络状况不佳的情…...
【大模型】AI 辅助编程操作实战使用详解
目录 一、前言 二、AI 编程介绍 2.1 AI 编程是什么 2.1.1 为什么需要AI辅助编程 2.2 AI 编程主要特点 2.3 AI编程底层核心技术 2.4 AI 编程核心应用场景 三、AI 代码辅助编程解决方案 3.1 AI 大模型平台 3.1.1 AI大模型平台代码生成优缺点 3.2 AI 编码插件 3.3 AI 编…...
Eclipse IDE 快捷键大全
文章目录 简介 ✨常用编辑快捷键 ⌨️基础编辑操作查找和定位代码优化 调试快捷键 🐛编辑器通用快捷键 📝窗口操作快捷键 🪟特殊功能快捷键 🔧重构相关快捷键 🔄提示 💡 简介 ✨ Eclipse 作为一款强大的集…...
ES面试题
1、Elasticsearch的基本构成: (1)index 索引: 索引类似于mysql 中的数据库,Elasticesearch 中的索引是存在数据的地方,包含了一堆有相似结构的文档数据。 (2)type 类型:…...
设置git区分大小写
设置git区分大小写 1.全局设置 (影响全部仓库): git config --global core.ignorecase false2.仓库级别设置 (影响当前仓库): git config core.ignorecase false3.已经提交了大小写不一致的文件处理: git mv -f OldName newName # 强制重命名 git commit -m "Fix cas…...
0205算法:最长连续序列、三数之和、排序链表
力扣128:最长连续序列 给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。 请你设计并实现时间复杂度为 O(n) 的算法解决此问题。 class Solution {public int longestConsecutive(in…...
VLAN 基础 | 不同 VLAN 间通信实验
注:本文为 “ Vlan 间通信” 相关文章合辑。 英文引文,机翻未校。 图片清晰度限于原文图源状态。 未整理去重。 How to Establish Communications between VLANs? 如何在 VLAN 之间建立通信? Posted on November 20, 2015 by RouterSwi…...
DeepSeek各版本说明与优缺点分析
DeepSeek各版本说明与优缺点分析 DeepSeek是最近人工智能领域备受瞩目的一个语言模型系列,其在不同版本的发布过程中,逐步加强了对多种任务的处理能力。本文将详细介绍DeepSeek的各版本,从版本的发布时间、特点、优势以及不足之处࿰…...
DS图(中)(19)
文章目录 前言一、图的遍历广度优先遍历深度优先遍历 二、最小生成树Kruskal算法Prim算法两种方法对比 总结 前言 承上启下,我们来学习下图的中篇!!! 一、图的遍历 图的遍历指的是遍历图中的顶点,主要有 广度优先遍历 …...
协同探索与导航文献整理
文章目录 1.SOAR:异构无人机协同探索与拍摄以实现快速自主重建2. RACER: 一种使用分散式无人机群进行快速协同探索的方法3. 使用协作式纳米无人机在非结构化环境中进行最小感知探索4.GVP-MREP:通过动态拓扑图上的 Voronoi 分区进行快速且通信高效的多无人机探索5.森林的快速多无…...
排序算法--计数排序
唯一种没有比较的排序(指没有前后比较,还是有交换的)。统计每个元素出现的次数,直接计算元素在有序序列中的位置,要求数据是整数且范围有限。适用于数据为小范围整数(如年龄、成绩),数据重复率较高时效率更优。可用于小…...
吴恩达深度学习——对象检测
内容来自https://www.bilibili.com/video/BV1FT4y1E74V,仅为本人学习所用。 文章目录 对象定位特征点检测基于滑动窗口的目标检测算法原理将全连接层转化成卷积层通过卷积实现滑动窗口检测算法 YOLOBounding Box预测交并比非极大值抑制Anchor BoxYOLO检测训练集中预…...
BUU19 [BJDCTF2020]Easy MD51
题目 当点进去不知道干啥的时候:1.看源代码 2.抓包 3.看网络请求 F12 用bp抓包,在response消息头中有hint提示: select * from admin where passwordmd5($pass,true) 如果md5($pass,true)后是 or 1 那么整句话就是password or 1&a…...
蓝桥杯刷题DAY2:二维前缀和 一维前缀和 差分数组
闪耀的灯光 📌 题目描述 蓝桥公园是一个适合夜间散步的好地方,公园可以被视为由 n m 个矩形区域构成。每个区域都有一盏灯,初始亮度为 a[i][j]。 小蓝可以选择一个大的矩形区域,并按下开关一次,这将使得该区域内每盏…...
HTB:EscapeTwo[WriteUP]
目录 连接至HTB服务器并启动靶机 信息收集 使用rustscan对靶机TCP端口进行开放扫描 将靶机TCP开放端口号提取并保存 使用nmap对靶机TCP开放端口进行脚本、服务扫描 使用nmap对靶机TCP开放端口进行漏洞、系统扫描 使用nmap对靶机常用UDP端口进行开放扫描 使用nmap对靶机…...
vue 引入百度地图和高德天气 都得获取权限
vue接入百度地图---获取ak https://blog.csdn.net/qq_57144407/article/details/143430661 vue接入高德天气, 需要授权----获取key https://www.jianshu.com/p/09ddd698eebe...
AI大模型:DeepSeek
近期DeepSeek产生了很大的影响力。首先来自于性能,给了业内一个很好的释放,缓解了HPC以及大规模集群被卡的焦虑。通过实验证实了小规模团队(公开资料显示规模约150左右)在资源受限的情况下(2M H100 GPU时),依然可以完成对领先大模型的实现与部署。后续观察该团队是否可以…...
LeetCode - #198 打家劫舍
网罗开发 (小红书、快手、视频号同名) 大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等…...
从离散傅里叶变换(DFT)到快速傅里叶变换(FFT)
摘要 离散傅里叶变换(DFT)是数字信号处理领域中分析信号频域特性的重要工具,但直接计算DFT的复杂度较高,限制了其在大规模数据处理中的应用。快速傅里叶变换(FFT)的出现显著降低了计算复杂度,极…...
【STM32】HAL库USB虚拟U盘MSC配置及采用自带的Flash作为文件系统
【STM32】HAL库USB虚拟U盘MSC实现配置及采用自带的Flash作为文件系统 本文将自带的Flash作为文件系统 通过配置USB的MSC功能实现虚拟U盘 没有单独建立FATFS文件系统 仅仅是配置USB和Flash读写而已 当然 这里也可以用外部Flash等等 也可以配置文件系统来进行套壳 但总体而言不如…...