MyBatis是什么
Mabits官方文档
在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.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.4.1version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.16version>
dependency>
<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<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>
<mappers>
<mapper resource="mappers/EmployeeMapper.xml" />
<mapper resource="mappers/DepartmentMapper.xml" />
<mapper resource="mappers/EmployeeMapper1.xml" />
mappers>
configuration>
MyBatis 的配置文件包含了影响 MyBatis 行为甚深的设置( settings)和属性( properties)信息。文档的顶层结构如下:
官方文档
<configuration>
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
settings-运行时行为设置
<configuration>
...
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
settings>
类型更多设置
类型处理器
typeAliases-别名
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。可以在xml配置
,也可以在类上加注解@Alias
不过为了好排查,一般都写全类名。
enviroments-运行环境
<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>
...
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 list = mapper.getEmp1();
System.out.println(empAndDepart);
sqlSession.commit();
sqlSession.close();
}
}
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
<mapper namespace="org.demo.mapper.EmployeeMapper">
<select id="getEmpById" resultType="org.demo.po.Employee">
select * from employee where id = #{id}
select>
<insert id="addEmp" parameterType="org.demo.po.Employee"
useGeneratedKeys="true" keyProperty="id" >
insert into employee(last_name,email,gender)
values(#{lastName},#{email},#{gender})
insert>
<update id="updateEmp">
update employee
set last_name=#{lastName},email=#{email},gender=#{gender}
where id=#{id}
update>
<delete id="deleteEmpById">
delete from employee where id=#{id}
delete>
mapper>
4种映射文件,多个参数会被封装成 一个map
<mapper namespace="org.demo.mapper.EmployeeMapper">
<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语句中;会有安全问题;流式查询指的是查询成功后不是返回一个集合而是返回一个迭代器,应用每次从迭代器取一条查询结果。流式查询的好处是能够降低内存使用。如果没有流式查询,我们想要从数据库取 1000 万条记录而又没有足够的内存时,就不得不分页查询,而分页查询效率取决于表设计,如果设计的不好,就无法执行高效的分页查询。
流式查询的过程当中,数据库连接是保持打开状态的,因此要注意的是:执行一个流式查询后,数据库访问框架就不负责关闭数据库连接了,需要应用在取完数据后自己关闭。
在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的方式
<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());
});
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 自动关闭。
首先定义好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 -> { });
}
}
本质上和方案二,只在外部调用时生效。在当前类中调用这个方法,依旧会报错。
public interface EmployeeMapper {
//多条记录封装一个map:Map:键是这条记录的主键,值是记录封装后的javaBean
//@MapKey:告诉mybatis封装这个map的时候使用哪个属性作为map的key
@MapKey("lastName")
public Map<String, Employee> getEmpByLastNameLikeReturnMap(String lastName);
//返回一条记录的map;key就是列名,值就是对应的值
public Map<String, Object> getEmpByIdReturnMap(Integer id);
...
<mapper namespace="org.demo.mapper.EmployeeMapper">
<resultMap id="MySimpleEmp" type="org.demo.po.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
resultMap>
<select id="getEmpByIdWithResultMap" resultMap="MySimpleEmp">
select * from employee where id=#{id}
select>
实体类和数据库如上述配置
创建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
文件(多对一或一对一)这里有三种查询方式
<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>
<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 property="department" javaType="Department">
<id column="id" property="id"/>
<result column="department_name" property="departmentName"/>
association>
resultMap>
<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 column="did" property="department" javaType="Department" select="getDepart">
<id column="id" property="id"/>
<result column="department_name" property="departmentName"/>
association>
resultMap>
<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对象的时候,都将一起查询出来。部门信息在我们使用的时候再去查询;分段查询的基础之上加上两个配置:在全局配置文件中配置,实现懒加载
...
...
创建DepartmentMapper.xml
,一对多关联查询-collection,有两种
<mapper namespace="org.demo.mapper.DepartmentMapper">
<resultMap type="org.demo.po.Department" id="MyDeptWithEmps">
<id column="id" property="id"/>
<result column="department_name" property="departmentName"/>
<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>
<resultMap type="org.demo.po.Department" id="MyDeptStep">
<id column="id" property="id"/>
<id column="department_name" property="departmentName"/>
<collection property="emps"
select="getEmpByDid"
column="id">
collection>
resultMap>
<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>
<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>
discriminator鉴别器,创建EmployeeMapper1.xml
<mapper namespace="org.demo.mapper.EmployeeMapper1">
<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"/>
<discriminator javaType="string" column="gender">
<case value="0" resultType="org.demo.po.Employee">
<association property="department"
select="getDeptById"
column="did" fetchType="eager" >
association>
case>
<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>
<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>
MyBatis采用功能强大的基于 OGNL 的表达式来简化操作。
严格来说,在XML中只有”<”和”&”是非法的,需要转义,其中转义字符
< |
< |
---|---|
> |
> |
& |
& |
' |
’ |
" |
" |
<mapper namespace="org.demo.mapper.DynamicSQLMapper">
<select id="getEmpsByConditionIf" resultType="org.demo.po.Employee">
select * from employee where 1=1
<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>
<if test="gender==0 or gender==1">
and gender=#{gender}
if>
select>
<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>
<if test="gender==0 or gender==1">
and gender=#{gender}
if>
where>
select>
<update id="updateEmp">
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}
update>
mapper>
SQL语句前面或后面多出的and或者**or **where
标签不能解决
<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>
<if test="gender==0 or gender==1">
gender=#{gender} and
if>
trim>
select>
<mapper namespace="com.lun.c04.dynamicsql.DynamicSQLMapper">
<select id="getEmpsByConditionChoose" resultType="org.demo.po.Employee">
select * from employee
<where>
<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>
#{变量名}
就能取出变量的值也就是当前遍历出的元素
<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>
<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>
<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>
<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>
<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>
抽取可重用的sql片段,方便后面引用:
#{}
,而使用${}
<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 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。
MyBatis系统中默认定义了两级缓存,一级缓存和二级缓存。
在pom.xml
中引入逆向工程MBG依赖
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.11version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.4.1version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.16version>
dependency>
<dependency>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-coreartifactId>
<version>1.3.7version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-maven-pluginartifactId>
<version>1.3.7version>
<dependencies>
<dependency>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-coreartifactId>
<version>1.3.7version>
dependency>
<dependency>
<groupId>com.mchangegroupId>
<artifactId>c3p0artifactId>
<version>0.9.2version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.16version>
dependency>
dependencies>
plugin>
plugins>
build>
在项目的resources目录下创建generatorConfig.xml
,修改部分内容,最后执行插件generate目标即可
<generatorConfiguration>
<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>
<javaModelGenerator targetPackage="org.demo.pojo" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
javaModelGenerator>
<sqlMapGenerator targetPackage="mappers"
targetProject=".\src\main\resources">
<property name="enableSubPackages" value="true" />
sqlMapGenerator>
<javaClientGenerator type="XMLMAPPER"
targetPackage="org.demo.mapper" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
javaClientGenerator>
<table tableName="department" domainObjectName="Department"/>
<table tableName="employee" domainObjectName="Employee"/>
context>
generatorConfiguration>
selectByExample
按条件查询,需要传入一个example对象或者null;如果传入一个null,则表示没有条件,也就是查询所有数据
example.createCriteria().xxx
创建条件对象,通过andXXX方法为SQL添加查询添加,每个条件之间是and关系
example.or().xxx
将之前添加的条件通过or拼接其他条件
updateByPrimaryKey
通过主键进行数据修改,如果某一个值为null,也会将对应的字段改为null
updateByPrimaryKeySelective()
通过主键进行选择性数据修改,如果某个值为null,则不修改这个字段
首先在pom.xml
中引入相关依赖
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelperartifactId>
<version>5.2.0version>
dependency>
在mybatis-config.xml
设置分页插件
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">plugin>
plugins>
在查询功能之前使用PageHelper.startPage(int pageNum, int pageSize)
开启分页功能
//访问第一页,每页四条数据
Page<Object> page = PageHelper.startPage(1, 4);
List<Emp> emps = mapper.selectByExample(null);
//在查询到List集合后,打印分页数据
System.out.println(page);
在查询获取list集合之后,使用PageInfo pageInfo = new PageInfo<>(List list, intnavigatePages)
获取分页相关数据
PageHelper.startPage(1, 4);
List<Emp> emps = mapper.selectByExample(null);
PageInfo<Emp> page = new PageInfo<>(emps,5);
System.out.println(page);
参考资料与其他文章
源码仓库
具体源码分析