一、简介
MyBatis是什么
- MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。
- MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
- MyBatis 可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO( Plain Old Java Objects,普通的Java对象)映射成数据库中的记录
二、Mybatis配置
1、数据准备
在MySQL数据库创建一数据库实例learnmybatis
,在其创建一张表
CREATE TABLE employee(
id INT(11) PRIMARY KEY AUTO_INCREMENT,
last_name VARCHAR(255),
gender CHAR(1),
email VARCHAR(255),
did VARCHAR(255)
);
CREATE TABLE department(
id INT(11) PRIMARY KEY AUTO_INCREMENT,
department_name VARCHAR(255)
);
创建对应的JavaBean
public class Employee {
private Integer id;
private String lastName;
private String email;
private String gender;
private Department department;
//getter and setter and toString()
}
public class Department {
private Integer id;
private String departmentName;
private List<Employee> emps;
//getter and setter and toString()
}
pom.xml
中引入依赖,注意引入最新版
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
2、Mybatis全局配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- 使用驼峰命名法 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 开启日志,这里也可以配置log4j来实现,不过需要配置-->
<setting name="logImpl" value="STDOUT_LOGGING" />
<!--二级缓存开启-->
<setting name="cacheEnabled" value="true"/>
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/learnmybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
<!-- 将我们写好的sql映射文件(EmployeeMapper.xml)一定要注册到全局配置文件(mybatis-config.xml)中 -->
<mappers>
<!--这里填写resource下的路径-->
<mapper resource="mappers/EmployeeMapper.xml" />
<mapper resource="mappers/DepartmentMapper.xml" />
<mapper resource="mappers/EmployeeMapper1.xml" />
<!--也可以直接填写包名,有好几种写法-->
</mappers>
</configuration>
MyBatis 的配置文件包含了影响 MyBatis 行为甚深的设置( settings)和属性( properties)信息。文档的顶层结构如下:
- configuration 配置
- properties 属性
- settings 设置
- typeAliases 类型命名
- typeHandlers 类型处理器
- objectFactory 对象工厂
- plugins 插件
- environments 环境
- environment 环境变量
- transactionManager 事务管理器
- dataSource 数据源
- databaseIdProvider 数据库厂商标识
- mappers 映射器
- environment 环境变量
3、全局配置文件-properties外部引入
<configuration>
<!--
1、mybatis可以使用properties来引入外部properties配置文件的内容;
resource:引入类路径下的资源
url:引入网络路径或者磁盘路径下的资源
-->
<properties resource ="dbconfig.properties"></properties>
在resource下中创建dbconfig.properties
文件
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/learnmybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=root
4、配置文件常用配置
settings-运行时行为设置
<configuration>
...
<!--
2、settings包含很多重要的设置项
setting:用来设置每一个设置项
name:设置项名
value:设置项取值
这里设置驼峰命名法
-->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
类型更多设置
typeAliases-别名
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。可以在xml配置<typeAliases>
,也可以在类上加注解@Alias
不过为了好排查,一般都写全类名。
enviroments-运行环境
- id:指定当前环境的唯一标识
- transactionManager、和dataSource都必须有
<environments default="dev_mysql">
<environment id="dev_mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
...
5、测试类
public class MybatisTest {
public static void main(String[] args) throws IOException {
// 加载mybatis框架主配置文件
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 读取解析配置文件内容,创建SqlSessionFacory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 获取sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
// 执行数据库操作
Employee empAndDepart = mapper.getEmpAndDepart(1);
// List<Employee> list = mapper.getEmp1();
System.out.println(empAndDepart);
sqlSession.commit();
sqlSession.close();
}
}
三、Mybatis 基础操作
1、基础操作
- cache –命名空间的二级缓存配置
- cache-ref – 其他命名空间缓存配置的引用。
- resultMap – 自定义结果集映射
- parameterMap – 已废弃!老式风格的参数映射
- sql –抽取可重用语句块。
- insert – 映射插入语句
- update – 映射更新语句
- delete – 映射删除语句
- select – 映射查询语句
EmployeeMapper.java
public interface EmployeeMapper {
//也可以通过注解方式
//@Select("select * from employee where id = #{id}")
// @Results({
// @Result(id=true,column="id",property="id")
// )}
// public Employee getEmpById(Integer id);
public Employee getEmpById(Integer id);
public Long addEmp(Employee employee);
public boolean updateEmp(Employee employee);
public void deleteEmpById(Integer id);
}
EmployeeMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 这里修改自己的mapper -->
<mapper namespace="org.demo.mapper.EmployeeMapper">
<select id="getEmpById" resultType="org.demo.po.Employee">
select * from employee where id = #{id}
</select>
<!-- public void addEmp(Employee employee);
插入自增返回主键 -->
<insert id="addEmp" parameterType="org.demo.po.Employee"
useGeneratedKeys="true" keyProperty="id" >
insert into employee(last_name,email,gender)
values(#{lastName},#{email},#{gender})
</insert>
<!-- public void updateEmp(Employee employee); -->
<update id="updateEmp">
update employee
set last_name=#{lastName},email=#{email},gender=#{gender}
where id=#{id}
</update>
<!-- public void deleteEmpById(Integer id); -->
<delete id="deleteEmpById">
delete from employee where id=#{id}
</delete>
</mapper>
- resultType:返回结果类型,可以做别名映射
- parameterType:参数类型,可以省略,
- 获取自增主键的值:
- mysql支持自增主键,自增主键值的获取,mybatis也是利用statement.getGenreatedKeys();
- useGeneratedKeys=“true”;使用自增主键获取主键值策略
- keyProperty;指定对应的主键属性,也就是mybatis获取到主键值以后,将这个值封装给javaBean的哪个属性
2、映射文件-参数处理
4种映射文件,多个参数会被封装成 一个map
<mapper namespace="org.demo.mapper.EmployeeMapper">
<!--在mybat配置文件配置好typeAliases后,resultType即可使用别名-->
<!-- 多个参数,不能直写id或lastName,否则抛出 org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [1, 0, param1, param2]-->
<select id="getEmpByIdAndLastName" resultType="org.demo.po.Employee">
select * from tbl_employee where id = #{id} and last_name=#{lastName}
</select>
<select id="getEmpByIdAndLastName2" resultType="org.demo.po.Employee">
select * from employee where id = #{0} and last_name=#{1}
</select>
<select id="getEmpByIdAndLastName3" resultType="org.demo.po.Employee">
select * from employee where id = #{param1} and last_name=#{param2}
</select>
<select id="getEmpByIdAndLastName4" resultType="org.demo.po.Employee">
select * from employee where id = #{id} and last_name=#{lastName}
</select>
...
public interface EmployeeMapper {
public Employee getEmpByIdAndLastName(Integer id, String name);
public Employee getEmpByIdAndLastName2(Integer id, String name);
public Employee getEmpByIdAndLastName3(Integer id, String name);
public Employee getEmpByIdAndLastName4(@Param("id")Integer id,@Param("lastName")String name);
...
参数处理#{}与${}取值区别
#{}
: 是以预编译的形式,将参数设置到sql语句中;PreparedStatement;防止sql注入${}
: 取出的值直接拼装在sql语句中;会有安全问题;
3、流式查询
3.1 简介
流式查询指的是查询成功后不是返回一个集合而是返回一个迭代器,应用每次从迭代器取一条查询结果。流式查询的好处是能够降低内存使用。如果没有流式查询,我们想要从数据库取 1000 万条记录而又没有足够的内存时,就不得不分页查询,而分页查询效率取决于表设计,如果设计的不好,就无法执行高效的分页查询。
流式查询的过程当中,数据库连接是保持打开状态的,因此要注意的是:执行一个流式查询后,数据库访问框架就不负责关闭数据库连接了,需要应用在取完数据后自己关闭。
3.2 流式查询接口
在EmployeeMapper
创建mapper方法,这里提供两种方式
// MySQL 默认情况下,创建 prepareStatement 时,就已经是 ResultSet.TYPE_FORWARD_ONLY 和 ResultSet.CONCUR_READ_ONLY ,所以这两个参数可加可不加
// @Select("select * from employee")
// @Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = Integer.MIN_VALUE)
// @ResultType(Employee.class)
void getEmpList(ResultHandler<Employee> resultHandler);
基于xml的方式
<!--FORWORD_ONLY 结果集的游标只能向下滚动-->
<!--SCROLL_INSENSITIVE 结果集的游标可以上下移动,当数据库变化时,当前结果集不变-->
<!--SCROLL_SENSITIVE 返回可滚动的结果集,当数据库变化时,当前结果集同步改变-->
<!--fetchSize MySQL驱动需要这个数字来明确开启流处理方式-->
<select id="getEmpList" resultType="Employee" resultSetType="FORWARD_ONLY" fetchSize="-2147483648">
select * from employee
</select>
测试用例
// 执行数据库操作
AtomicInteger num = new AtomicInteger();
mapper.getEmpList(resultContext -> {
//逐条返回
Employee employee = resultContext.getResultObject();
/**
* 业务代码
*/
num.getAndIncrement();
System.out.println("第" + num + "次查询," + employee.toString());
});
3.3 流式查询接口–Cursor
MyBatis 提供了一个叫 org.apache.ibatis.cursor.Cursor
的接口类用于流式查询,这个接口继承了 java.io.Closeable
和 java.lang.Iterable
接口,由此可知:
-
Cursor 是可关闭的,实际上当关闭 Cursor 时,也一并将数据库连接关闭了;
-
Cursor 是可遍历的
除此之外,Cursor 还提供了三个方法:
isOpen()
:用于在取数据之前判断 Cursor 对象是否是打开状态。只有当打开时 Cursor 才能取数据;isConsumed()
:用于判断查询结果是否全部取完;getCurrentIndex()
:返回已经获取了多少条数据。
因为 Cursor 实现了迭代器接口,因此在实际使用当中,从 Cursor 取数据非常简单:
try(Cursor cursor = mapper.querySomeData()) {
cursor.forEach(rowObject -> {
// ...
});
}
使用 try-resource 方式可以令 Cursor 自动关闭。
3.4 流式查询接口–Cursor实现
首先定义好mapper方法
@Select("select * from employee limit #{limit}")
Cursor<Employee> scan(@Param("limit") int limit);
因为Cursor在取数据的过程中需要保持数据库连接,而 Mapper 方法通常在执行完后连接就关闭了,因此 Cusor 也一并关闭了,这里可能会报错,所以在spring整合的时候,有三种方法可以选择
-
SqlSessionFactory
用 SqlSessionFactory 来手工打开数据库连接。
@GetMapping("scan/1/{limit}") public void scanFoo1(@PathVariable("limit") int limit) throws Exception { try ( SqlSession sqlSession = sqlSessionFactory.openSession(); // 1 Cursor<Employee> cursor = sqlSession.getMapper(EmployeeMapper.class).scan(limit) // 2 ) { cursor.forEach(e -> { }); } }
上述代码中,1 处我们开启了一个 SqlSession (实际上也代表了一个数据库连接),并保证它最后能关闭;2 处我们使用 SqlSession 来获得 Mapper 对象。这样才能保证得到的 Cursor 对象是打开状态的
-
TransactionTemplate
在 Spring 中,我们可以用 TransactionTemplate 来执行一个数据库事务,这个过程中数据库连接同样是打开的
@GetMapping("scan/2/{limit}") public void scanFoo2(@PathVariable("limit") int limit) throws Exception { TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); // 1 transactionTemplate.execute(status -> { // 2 try (Cursor<Employee> cursor = EmployeeMapper.scan(limit)) { cursor.forEach(e -> { }); } catch (IOException e) { e.printStackTrace(); } return null; }); }
上面的代码中,1 处我们创建了一个 TransactionTemplate 对象(此处 transactionManager 是怎么来的不用多解释,本文假设读者对 Spring 数据库事务的使用比较熟悉了),2 处执行数据库事务,而数据库事务的内容则是调用 Mapper 对象的流式查询。注意这里的 Mapper 对象无需通过 SqlSession 创建。
-
@Transactional 注解
@GetMapping("scan/3/{limit}") @Transactional public void scanFoo3(@PathVariable("limit") int limit) throws Exception { try (Cursor<Employee> cursor = EmployeeMapper.scan(limit)) { cursor.forEach(e -> { }); } }
本质上和方案二,只在外部调用时生效。在当前类中调用这个方法,依旧会报错。
四、Mybatis 高级操作(一对多、多对一)
1、封装映射
1.1 map(select-记录封装)
public interface EmployeeMapper {
//多条记录封装一个map:Map<Integer,Employee>:键是这条记录的主键,值是记录封装后的javaBean
//@MapKey:告诉mybatis封装这个map的时候使用哪个属性作为map的key
@MapKey("lastName")
public Map<String, Employee> getEmpByLastNameLikeReturnMap(String lastName);
//返回一条记录的map;key就是列名,值就是对应的值
public Map<String, Object> getEmpByIdReturnMap(Integer id);
...
1.2 自定义结果映射规则(select-resultMap)
<mapper namespace="org.demo.mapper.EmployeeMapper">
<!--自定义某个javaBean的封装规则
type:自定义规则的Java类型
id:唯一id方便引用
-->
<resultMap id="MySimpleEmp" type="org.demo.po.Employee">
<!--指定主键列的封装规则
id定义主键会底层有优化;
column:指定哪一列
property:指定对应的javaBean属性
-->
<id column="id" property="id"/>
<!-- 定义普通列封装规则 -->
<result column="last_name" property="lastName"/>
<!-- 其他不指定的列会自动封装:我们只要写resultMap就把全部的映射规则都写上。 -->
<result column="email" property="email"/>
<result column="gender" property="gender"/>
</resultMap>
<!-- resultMap:自定义结果集映射规则; -->
<!-- public Employee getEmpById(Integer id); -->
<select id="getEmpByIdWithResultMap" resultMap="MySimpleEmp">
select * from employee where id=#{id}
</select>
2、关联查询
实体类和数据库如上述配置
2.1 多对一处理
创建EmployeeMapper
接口文件
public interface EmployeeMapper {
Employee getEmpAndDepart(Integer id);
List<Employee> getEmpAndDepart1();
List<Employee> getEmpAndDepart2();
@Select("select * from employee where id = #{id}")
@Options
Employee getEmpById(@Param("id") Integer id);
}
mapper.xml
文件(多对一或一对一)这里有三种查询方式
- 联合查询
- 指定联合的javaBean对象查询
- 分步查询
<!-- 这里修改自己的mapper -->
<mapper namespace="org.demo.mapper.EmployeeMapper">
<!-- 联合查询:级联属性封装结果集-->
<resultMap id="MyEmployee" type="org.demo.po.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="id" property="department.id"/>
<result column="department_name" property="department.departmentName"/>
</resultMap>
<!-- association可以指定联合的javaBean对象 -->
<resultMap id="MyEmployee1" type="org.demo.po.Employee">
<result column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
<!-- association可以指定联合的javaBean对象
property="department":指定哪个属性是联合的对象
javaType:指定这个属性对象的类型[不能省略]
-->
<association property="department" javaType="Department">
<id column="id" property="id"/>
<result column="department_name" property="departmentName"/>
</association>
</resultMap>
<!-- association分步查询 -->
<!-- 使用association进行分步查询:
1、先按照员工id查询员工信息
2、根据查询员工信息中的did值去部门表查出部门信息
3、部门设置到员工中;
-->
<resultMap id="MyEmployee2" type="org.demo.po.Employee">
<result column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
<!-- association定义关联对象的封装规则
select:表明当前属性是调用select指定的方法查出的结果
column:指定将哪一列的值传给这个方法
流程:使用select指定的方法(传入column指定的这列参数的值)查出对象,并封装给property指定的属性
-->
<association column="did" property="department" javaType="Department" select="getDepart">
<id column="id" property="id"/>
<result column="department_name" property="departmentName"/>
</association>
</resultMap>
<!-- public Employee getEmpAndDept(Integer id);-->
<select id="getEmpAndDepart" resultMap="MyEmployee">
SELECT *
FROM employee e, department d
WHERE e.did=d.id AND e.id=#{id}
</select>
<select id="getEmpAndDepart1" resultMap="MyEmployee">
select * from employee e, department d WHERE e.did =d.id
</select>
<select id="getEmpAndDepart2" resultMap="MyEmployee">
select * from employee
</select>
<select id="getDepart" resultType="Department">
select * from department where id = #{did}
</select>
</mapper>
延迟加载
我们每次查询Employee对象的时候,都将一起查询出来。部门信息在我们使用的时候再去查询;分段查询的基础之上加上两个配置:在全局配置文件中配置,实现懒加载
<configuration>
...
<settings>
...
<!--显示的指定每个我们需要更改的配置的值,即使他是默认的。防止版本更新带来的问题 -->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
2.2 一对多处理
创建DepartmentMapper.xml
,一对多关联查询-collection,有两种
- 联合查询
- 分步查询
<!-- 这里修改自己的mapper -->
<mapper namespace="org.demo.mapper.DepartmentMapper">
<!--嵌套结果集的方式,使用collection标签定义关联的集合类型的属性封装规则 -->
<resultMap type="org.demo.po.Department" id="MyDeptWithEmps">
<id column="id" property="id"/>
<result column="department_name" property="departmentName"/>
<!--
collection定义关联集合类型的属性的封装规则
ofType:指定集合里面元素的类型
-->
<collection property="emps" ofType="org.demo.po.Employee" javaType="java.util.List">
<!-- 定义这个集合中元素的封装规则 -->
<id column="e_id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
</collection>
</resultMap>
<!-- collection:分段查询 -->
<resultMap type="org.demo.po.Department" id="MyDeptStep">
<id column="id" property="id"/>
<id column="department_name" property="departmentName"/>
<!--多列传值column="{key1=column1,key2=column2}"-->
<collection property="emps"
select="getEmpByDid"
column="id">
</collection>
</resultMap>
<!-- Department MyDept(Integer id); -->
<!--这里因为两个id重名了,Mybatis无法分辨,所以需要重命名一下-->
<select id="MyDept" resultMap="MyDeptWithEmps">
SELECT d.*, e.*,e.id as e_id
FROM department d LEFT JOIN employee e ON d.id=e.did
WHERE d.id=#{id}
</select>
<!--Department getDeptStep(Integer id);-->
<select id="getDeptStep" resultMap="MyDeptStep">
select * from department
where id = #{id}
</select>
<select id="getEmpByDid" resultType="org.demo.po.Employee">
select * from employee where did = #{id}
</select>
</mapper>
3、鉴别器
discriminator鉴别器,创建EmployeeMapper1.xml
<!-- 这里修改自己的mapper -->
<mapper namespace="org.demo.mapper.EmployeeMapper1">
<!-- =======================鉴别器============================ -->
<!-- <discriminator javaType=""></discriminator>
鉴别器:mybatis可以使用discriminator判断某列的值,然后根据某列的值改变封装行为
封装Employee:
如果查出的是女生:就把部门信息查询出来,否则不查询;
如果是男生,把last_name这一列的值赋值给email;
-->
<resultMap type="org.demo.po.Employee" id="MyEmpDis">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
<!--
column:指定判定的列名
javaType:列值对应的java类型,忽视大小写
-->
<discriminator javaType="string" column="gender">
<!--女生 resultType:指定封装的结果类型;不能缺少。/resultMap-->
<case value="0" resultType="org.demo.po.Employee">
<association property="department"
select="getDeptById"
column="did" fetchType="eager" >
</association>
</case>
<!--男生 ;如果是男生,把last_name这一列的值赋值给email; -->
<case value="1" resultType="org.demo.po.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="last_name" property="email"/>
<result column="gender" property="gender"/>
</case>
</discriminator>
</resultMap>
<!-- List<Employee> getEmpsWithDiscriminator();-->
<select id="getEmpsWithDiscriminator" resultMap="MyEmpDis">
select * from employee
</select>
<select id="getDeptById" resultType="org.demo.po.Department">
select * from department where id = #{did}
</select>
</mapper>
4、动态sql
4.1 简介
MyBatis采用功能强大的基于 OGNL 的表达式来简化操作。
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
严格来说,在XML中只有”<”和”&”是非法的,需要转义,其中转义字符
< | < |
---|---|
> | > |
& | & |
' | ’ |
" | " |
4.2 if判断
<!-- 这里修改自己的mapper -->
<mapper namespace="org.demo.mapper.DynamicSQLMapper">
<!-- 查询员工,要求,携带了哪个字段查询条件就带上这个字段的值 -->
<!-- public List<Employee> getEmpsByConditionIf(Employee employee); -->
<select id="getEmpsByConditionIf" resultType="org.demo.po.Employee">
select * from employee where 1=1
<!-- test:判断表达式(OGNL)
OGNL参照PPT或者官方文档。
c:if test
从参数中取值进行判断
遇见特殊符号应该去写转义字符:
&&:
-->
<if test="id!=null">
and id=#{id}
</if>
<if test="lastName!=null && lastName!=""">
and last_name like #{lastName}
</if>
<if test="email!=null and email.trim()!=""">
and email=#{email}
</if>
<!-- ognl会进行字符串与数字的转换判断 "0"==0 -->
<if test="gender==0 or gender==1">
and gender=#{gender}
</if>
</select>
<!--
查询的时候如果某些条件没带可能sql拼装会有问题
1. 给where后面加上1=1,以后的条件都and xxx。
2. mybatis使用where标签来将所有的查询条件包括在内。mybatis就会将where标签中拼装的sql,
多出来的and或者or去掉(where只会去掉第一个多出来的and或者or,但最后一个多出来的and或者or则不会去掉)。
-->
<select id="getEmpsByConditionIfWithWhere" resultType="org.demo.po.Employee">
select * from employee
<where>
<if test="id!=null">
id=#{id}
</if>
<if test="lastName!=null and lastName!=""">
and last_name like #{lastName}
</if>
<if test="email!=null and email.trim()!=""">
and email=#{email}
</if>
<!-- ognl会进行字符串与数字的转换判断 "0"==0 -->
<if test="gender==0 or gender==1">
and gender=#{gender}
</if>
</where>
</select>
<!--Integer updateEmp(Employee employee);-->
<update id="updateEmp">
<!-- Set标签的使用 -->
update employee
<set>
<if test="lastName!=null">
last_name=#{lastName},
</if>
<if test="email!=null">
email=#{email},
</if>
<if test="gender!=null">
gender=#{gender}
</if>
</set>
where id=#{id}
<!--
Trim:更新拼串
update employee
<trim prefix="set" suffixOverrides=",">
<if test="lastName!=null">
last_name=#{lastName},
</if>
<if test="email!=null">
email=#{email},
</if>
<if test="gender!=null">
gender=#{gender}
</if>
</trim>
where id=#{id} -->
</update>
</mapper>
4.3 trim-自定义字符串截取
SQL语句前面或后面多出的and或者**or **where
标签不能解决
- prefix="":前缀:trim标签体中是整个字符串拼串后的结果。
- prefix给拼串后的整个字符串加一个前缀
- prefixOverrides=""
- 前缀覆盖: 去掉整个字符串前面多余的字符
- suffix="":后缀
- suffix给拼串后的整个字符串加一个后缀
- suffixOverrides=""
- 后缀覆盖:去掉整个字符串后面多余的字符
<!--public List<Employee> getEmpsByConditionTrim(Employee employee); -->
<select id="getEmpsByConditionTrim" resultType="org.demo.po.Employee">
select * from employee
<!-- 自定义字符串的截取规则 -->
<trim prefix="where" suffixOverrides="and">
<if test="id!=null">
id=#{id} and
</if>
<if test="lastName!=null && lastName!=""">
last_name like #{lastName} and
</if>
<if test="email!=null and email.trim()!=""">
email=#{email} and
</if>
<!-- ognl会进行字符串与数字的转换判断 "0"==0
最后的and会自动剪裁-->
<if test="gender==0 or gender==1">
gender=#{gender} and
</if>
</trim>
</select>
4.4 choose-分支选择
<mapper namespace="com.lun.c04.dynamicsql.DynamicSQLMapper">
<!-- public List<Employee> getEmpsByConditionChoose(Employee employee); -->
<select id="getEmpsByConditionChoose" resultType="org.demo.po.Employee">
select * from employee
<where>
<!-- 如果带了id就用id查,如果带了lastName就用lastName查;只会进入其中一个 -->
<choose>
<when test="id!=null">
id=#{id}
</when>
<when test="lastName!=null">
last_name like #{lastName}
</when>
<when test="email!=null">
email = #{email}
</when>
<otherwise>
gender = 0
</otherwise>
</choose>
</where>
</select>
</mapper>
4.5 foreach-遍历集合
- collection:指定要遍历的集合:
- list类型的参数会特殊处理封装在map中,map的key就叫list
- item:将当前遍历出的元素赋值给指定的变量
- separator:每个元素之间的分隔符
- open:遍历出所有结果拼接一个开始的字符
- close:遍历出所有结果拼接一个结束的字符
- index:索引。遍历list的时候是index就是索引,item就是当前值
- 遍历map的时候index表示的就是map的key,item就是map的值
#{变量名}
就能取出变量的值也就是当前遍历出的元素
<!--public List<Employee> getEmpsByConditionForeach(List<Integer> ids); -->
<select id="getEmpsByConditionForeach" resultType="org.demo.po.Employee">
select * from employee
<foreach collection="ids" item="item_id" separator=","
open="where id in(" close=")">
#{item_id}
</foreach>
</select>
<!--List<Employee> selectEmp(Map map)
map的结构是{"list",list},list里是id
-->
<select id="selectEmp" parameterType="map" resultType="org.demo.po.Employee">
select * from employee
<where>
<foreach collection="list" item="id" open="(" separator="or" close=")">
id = #{id}
</foreach>
</where>
</select>
<!--List<Employee> selectEmp1(Map map)
map的结构是{"list",list},list里是id
-->
<select id="selectEmp1" parameterType="map" resultType="org.demo.po.Employee">
select * from employee
<where>
id in
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</where>
</select>
<!-- 批量保存 -->
<!--public void addEmps(@Param("emps")List<Employee> emps); -->
<!--MySQL下批量保存:可以foreach遍历 mysql支持values(),(),()语法-->
<insert id="addEmps">
insert into employee(last_name,email,gender,did)
values
<foreach collection="emps" item="emp" separator=",">
(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.department.id})
</foreach>
</insert><!-- -->
<!-- 这种方式需要数据库连接属性allowMultiQueries=true;
这种分号分隔多个sql可以用于其他的批量操作(删除,修改) -->
<insert id="addEmps2">
<foreach collection="emps" item="emp" separator=";">
insert into employee(last_name,email,gender,did)
values(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.department.id})
</foreach>
</insert>
4.6 sql抽取可重用的sql片段
抽取可重用的sql片段,方便后面引用:
- sql抽取:经常将要查询的列名,或者插入用的列名抽取出来方便引用
- include来引用已经抽取的sql:
- include还可以自定义一些property,sql标签内部就能使用自定义的属性
- include-property:取值的正确方式${prop},
- 不能使用
#{}
,而使用${}
<!-- 这里修改自己的mapper -->
<mapper namespace="org.demo.mapper.DynamicSQLMapper">
<sql id="employColumns">${alias}.id,${alias}.last_name,${alias}.gender,${alias}.email </sql>
<select id="selectEmployeeBySql" resultType="org.demo.po.Employee">
select
<include refid="employColumns"><property name="alias" value="t1"/></include>
from employee t1
</select>
<sql id="insertColumn">
<if test="_databaseId=='oracle'">
last_name,email,gender,did
</if>
<if test="_databaseId=='mysql'">
last_name,email,gender,did
</if>
</sql>
<insert id="addEmps3">
insert into employee(
<include refid="insertColumn"></include><!-- 使用地方 -->
)
values
<foreach collection="emps" item="emp" separator=",">
(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.department.id})
</foreach>
</insert>
</mapper>
五、缓存
MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。
MyBatis系统中默认定义了两级缓存,一级缓存和二级缓存。
- 默认情况下,只有一级缓存( SqlSession级别的缓存,也称为本地缓存)开启。
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
- 为了提高扩展性。 MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
- 对于Mabtis查询,查询过程默认是二级缓存->一级缓存->数据库
六、Mybatis逆向工程
1、逆向工程创建
在pom.xml
中引入逆向工程MBG依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.7</version>
</dependency>
</dependencies>
<!-- 控制Maven在构建过程中相关配置 -->
<build>
<!-- 构建过程中用到的插件 -->
<plugins>
<!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.7</version>
<!-- 插件的依赖 -->
<dependencies>
<!-- 逆向工程的核心依赖 -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.7</version>
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.2</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
在项目的resources目录下创建generatorConfig.xml
,修改部分内容,最后执行插件generate目标即可
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--
targetRuntime: 执行生成的逆向工程的版本
MyBatis3Simple: 生成基本的CRUD(清新简洁版)
MyBatis3: 生成带条件的CRUD(奢华尊享版)
-->
<context id="DB2Tables" targetRuntime="MyBatis3Simple">
<!-- 数据库的连接信息 -->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/learnmybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"
userId="root"
password="root">
</jdbcConnection>
<!-- javaBean的生成策略-->
<javaModelGenerator targetPackage="org.demo.pojo" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- SQL映射文件的生成策略 -->
<sqlMapGenerator targetPackage="mappers"
targetProject=".\src\main\resources">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- Mapper接口的生成策略 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="org.demo.mapper" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- 逆向分析的表 -->
<!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
<!-- domainObjectName属性指定生成出来的实体类的类名 -->
<table tableName="department" domainObjectName="Department"/>
<table tableName="employee" domainObjectName="Employee"/>
</context>
</generatorConfiguration>
2、MBG相关类CURD
-
selectByExample
按条件查询,需要传入一个example对象或者null;如果传入一个null,则表示没有条件,也就是查询所有数据
-
example.createCriteria().xxx
创建条件对象,通过andXXX方法为SQL添加查询添加,每个条件之间是and关系
-
example.or().xxx
将之前添加的条件通过or拼接其他条件
-
updateByPrimaryKey
通过主键进行数据修改,如果某一个值为null,也会将对应的字段改为null
-
updateByPrimaryKeySelective()
通过主键进行选择性数据修改,如果某个值为null,则不修改这个字段
七、分页插件
1、插件配置
首先在pom.xml
中引入相关依赖
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
在mybatis-config.xml
设置分页插件
<plugins>
<!--设置分页插件-->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
2、插件使用
2.1 直接输出
在查询功能之前使用PageHelper.startPage(int pageNum, int pageSize)
开启分页功能
- pageNum:当前页的页码
- pageSize:每页显示的条数
//访问第一页,每页四条数据
Page<Object> page = PageHelper.startPage(1, 4);
List<Emp> emps = mapper.selectByExample(null);
//在查询到List集合后,打印分页数据
System.out.println(page);
2.2 使用PageInfo
在查询获取list集合之后,使用PageInfo pageInfo = new PageInfo<>(List list, intnavigatePages)
获取分页相关数据
- list:分页之后的数据
- navigatePages:导航分页的页码数
PageHelper.startPage(1, 4);
List<Emp> emps = mapper.selectByExample(null);
PageInfo<Emp> page = new PageInfo<>(emps,5);
System.out.println(page);
3、分页常用数据
- pageNum:当前页的页码
- pageSize:每页显示的条数
- size:当前页显示的真实条数
- total:总记录数
- pages:总页数
- prePage:上一页的页码
- nextPage:下一页的页码
- isFirstPage/isLastPage:是否为第一页/最后一页
- hasPreviousPage/hasNextPage:是否存在上一页/下一页
- navigatePages:导航分页的页码数
- navigatepageNums:导航分页的页码,[1,2,3,4,5]
参考资料与其他文章