Mybatis

Posted by 余腾 on 2019-04-03
Estimated Reading Time 41 Minutes
Words 9.1k In Total
Viewed Times

MyBatis手册入口——从入门到放弃

框架:整体解决方案

  • JDBC Dbutils(QueryRunner) JdbcTemplate:都仅仅是工具

编写sql -> 预编译 -> 设置参数 -> 执行sql-> 封装结果 -> DbRecords

  • Hibernate:全自动全映射ORM(Object Relation Mapping)框架;使用HQL(支持定制SQL)

  • MyBatis :半自动,轻量级的框架。Sql与java编码分离,可以定制优化SQL 不失去灵活性。

    • 把编写sql语句 的功能抽出交给配置文件来完成。
    • 功能边界清晰,一个专注业务,一个专注数据。

一、从 XML 中构建 SqlSessionFactory

  • 基本 jar包
    • log4j.jar
    • mybatis-3.4.1.jar
    • mysql-connector-java-5.1.37-bin.jar

创建 mybatis-config.xml 配置文件

  • XML 配置文件中包含了对 MyBatis 系统的核心设置
  • 包含获取数据库连接实例的数据源(DataSource)和决定事务作用域和控制方式的事务管理器(TransactionManager),但是整合Spring 之后 里面的大部分配置交给Spring处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
<!-- 将我们写好的sql映射文件(BlogMapper.xml)一定要注册到全局配置文件(mybatis-config.xml)中 -->
<mappers>
<mapper resource="Blog.xml" />
</mappers>
</configuration>
  • 要注意 XML 头部的声明,它用来验证 XML 文档正确性。
  • environment 元素体中包含了事务管理和连接池的配置。
  • mappers 元素则是包含一组映射器(mapper),这些映射器的 XML 映射文件包含了 SQL 代码和映射定义信息。


  • 每个基于MyBatis的应用都是以一个 SqlSessionFactory 的实例为核心的。

  • SqlSessionFactory实例可以通过 SqlSessionFactoryBuilder 获得。

    • 而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先定制的 Configuration 的实例构建出 SqlSessionFactory 的实例。
  • SqlSessionFactoryBuilder 这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。

    • SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
  • SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在 没有任何理由丢弃它或重新创建另一个实例

    • 最佳实践是在应用运行期间不要重复创建多次 最佳作用域是应用作用域 (使用单例模式或者静态单例模式)
  • SqlSession 每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的最佳的作用域是请求或方法作用域

  • 映射器实例 映射器是一些由你创建的、绑定你映射的语句的接口。映射器接口的实例是从 SqlSession 中获得的。最大作用域是和请求它们的 SqlSession 相同的 但最好方法作用域

1
2
3
4
5
6
public class MyBatisTest {
public SqlSessionFactory getSqlSessionFactory() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}


二、获取 SqlSession

从 SqlSessionFactory 中获取 SqlSession

  • SqlSession 完全包含了面向数据库执行 SQL 命令所需的所有方法。获取sqlSession实例,能直接执行已经映射的sql语句。
  • 一个sqlSession就是代表和数据库的一次会话,用完关闭。
1
2
3
4
5
6
7
8
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession session = sqlSessionFactory.openSession();
try {
Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
//建议命名空间加唯一标识符id
} finally {
session.close();
}

👆 此时 selectOne 第一个参数是👇 XML 文件的 namespace + id,来进行查询

1
2
3
4
5
6
7
<?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 namespace="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>


接口式编程 XxxMapper.class 映射器

  • sql映射文件:配置了每一个sql,以及sql的封装规则等。将sql映射文件注册在全局配置文件中

接口式编程:

  • 原生: Dao ====> DaoImpl
  • Mybatis: Mapper ====> XxxMapper.xml

使用 XxxMapper.class 映射器的好处

  • 使用正确描述每个语句的参数和返回值的接口(比如 BlogMapper.class),不仅可以执行更清晰和类型安全的代码,而且还不用担心易错的字符串字面值以及强制类型转换。

SqlSession 和 connection一样 都是非线程安全。每次使用都应该去获取新的对象。

  • 一个sqlSession就是代表和数据库的一次会话,用完关闭。
  • mapper接口没有实现类,但是Mybatis会为这个接口生成一个代理对象。去执行增删改查方法
  • Mybatis将接口和xml进行绑定

两个重要的配置文件:

  • mybatis的全局配置文件:包含数据库连接池信息,事务管理器信息…系统运行环境信息
  • sql映射文件:保存了每一个sql语句的映射信息、将sql抽取出来。

映射器 XxxMapper.xml

1
2
3
4
5
6
7
<?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 namespace="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>
  • namespace:名称空间,指定为接口的全类名
  • id:唯一标识符。使用sql的唯一标志来告诉MyBatis执行哪个sql。接口中的方法名称
  • resultType:返回值类型。
  • #{id}:从传递过来的参数中取出id值。
  • SQL语句 可以起别名 比如 last_name lastName

示例

1
2
3
public interface BlogMapper {
public Blog getEmpById(Integer id);
}
1
2
3
4
5
6
7
8
9
10
11
SqlSession session = sqlSessionFactory.openSession();
try {
//获取接口的实现类对象
//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
System.out.println(mapper.getClass());
System.out.println(blog);
} finally {
session.close();//一定要关闭
}


Java 注解方式

  • 如果你需要完成很复杂的事情,那么最好使用 XML 来映射语句。
  • 基于注解和 XML 的语句映射方式间自由移植和切换。
1
2
3
4
5
package org.mybatis.example;
public interface BlogMapper {
@Select("SELECT * FROM blog WHERE id = #{id}")
Blog selectBlog(int id);
}


三、mybatis-config.xml 配置文件

MyBatis 配置文件

  • 标签使用是有顺序的
    • properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?

属性(properties)

  • mybatis 可以使用 <properties> 来引入外部 properties 配置文件的内容
    • resource:引入类路径下的资源
    • url:引入网络路径或者磁盘路径下的资源
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      <properties resource="dbconfig.properties"></properties>

      <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>


设置(settings)

  • settings包含很多重要的设置项
    • setting:用来设置每一个设置项
    • name:设置项名
    • value:设置项取值

一个配置完整的 settings (常用)元素的示例如下(官方文档示例):

1
2
3
4
5
6
7
<settings>
<!--显示的指定每个我们需要更改的配置的值,即使他是默认的。防止版本更新带来的问题 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>//使用驼峰命名
<setting name="lazyLoadingEnabled" value="true"/>//懒加载
<setting name="aggressiveLazyLoading" value="false"/>// false 按需加载
<setting name="cacheEnabled" value="true"/>//开启缓存
</settings>


类型别名(typeAliases)

  • 类型别名是为 Java 类型设置一个短的名字。 它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。默认别名就是类名小写 blog 和 select resultType=”blog” 配合使用 (别名不区分大小写)
1
2
3
<typeAliases>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
</typeAliases>
  • package:为某个包下的所有类批量起别名。
    • name:指定包名。为当前包以及子代所有包的每一个类都起一个默认别名(类名小写)
    • 防止别名冲突 用注解方式解决 @Alias (若有注解,则别名为其注解值)
1
2
3
<typeAliases>
<package name="domain.blog"/>
</typeAliases>
1
2
3
4
@Alias("blog")
public class Blog {
...
}


类型处理器(typeHandlers)

  • 无论是 MyBatis 在预处理语句中设置一个参数时,还是从结果集中取出一个值时都会用类型处理器将获取的值以合适的方式转换成 Java 类型。
  • 从 3.4.5 开始,MyBatis 默认支持 JSR-310(日期和时间 API) 自动注册处理器。


插件(plugins)

MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。

  • 默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
  • 拦截的四大对象
    • Executor (update, query, flushStatements, commit, rollback, getTransaction, close…)
    • ParameterHandler (getParameterObject, setParameters)
    • ResultSetHandler (handleResultSets, handleOutputParameters)
    • StatementHandler (prepare, parameterize, batch, update, query)


环境配置(environments)

  • environments:mybatis可以配置多种环境 ,default指定使用某种环境。可以快速切换环境。
  • 尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
    • 每个数据库对应一个 SqlSessionFactory 实例。
  • environment:配置一个具体的环境信息 id代表当前环境的唯一标识;
    • 必须有两个标签
      • transactionManager:事务管理器;
        • 如果正在使用 Spring + MyBatis,则没有必要配置事务管理器。因为 Spring 模块会使用自带的管理器来覆盖配置。
        • type:事务管理器的类型、JDBC(JdbcTransactionFactory)|MANAGED(ManagedTransactionFactory)
        • 自定义事务管理器:实现TransactionFactory接口。type指定为全类名
      • dataSource:数据源;
        • type;数据源类型; UNPOOLED(UnpooledDataSourceFactory)|POOLED(PooledDataSourceFactory)|JNDI(JndiDataSourceFactory)
        • 自定义数据源:实现DataSourceFactory接口,type是全类名


数据库厂商标识(DBIdProvider)

1
2
3
4
5
6
7
//作用就是得到数据库厂商的标识(驱动getDatabaseProductName()),Mybatis就能根据数据库厂商标识来执行不同的sql;
<databaseIdProvider type="DB_VENDOR">
<!-- 为不同的数据库厂商起别名 -->
<property name="MySQL" value="mysql"/>
<property name="Oracle" value="oracle"/>
<property name="SQL Server" value="sqlserver"/>
</databaseIdProvider>
  • 用 databaseId=”” 确定数据库使用那个SQL语句
1
2
3
<select id="getEmpById" resultType="Employee" databaseId="mysql">
select * from tbl_employee where id = #{id}
</select>


映射器(mappers)

  • mappers:将sql映射注册到全局配置中。
  • 注册配置文件
1
2
3
4
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
</mappers>
1
2
3
4
<!-- 使用完全限定资源定位符(URL)引用网络或者磁盘下文件 -->
<mappers>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
</mappers>

注册接口方式

  • class:引用(注册)接口,
    1、有sql映射文件,XxxMapper.xml 文件名必须和接口同名,并且放在与接口同一目录下;
    2、没有sql映射文件,所有的sql都是利用注解写在接口方法上;

  • 推荐:

    • 比较重要的,复杂的Dao接口我们来写sql映射文件。
    • 不重要,简单的Dao接口为了开发快速可以使用注解;
1
2
3
4
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.BlogMapper"/>
</mappers>
  • 配置文件的 包名称和文件名称都与接口相同 此时就可以分开。
  • 放在conf文件下 视觉效果分开 实际上还在一起
    1
    2
    3
    4
    <!-- 将包内的映射器接口实现全部注册为映射器 -->
    <mappers>
    <package name="org.mybatis.builder"/>
    </mappers>

对象工厂(objectFactory): 用默认的对象工厂(objectFactory)


四、MyBatis XML 映射文件

MyBatis 映射文件

XxxMapper.xml 详解

1
2
3
<select id="selectPerson" parameterType="int" resultType="hashmap">
SELECT * FROM PERSON WHERE ID = #{id}
</select>
  • parameterType: 参数类型。可以不传,Mybatis会根据TypeHandler自动推断类型。

  • resultType: 返回值类型(自动封装类型)。如果返回是集合,定义集合元素的类型。不能同时和 ResultMap 同时使用。

  • 单个参数:mybatis不会做特殊处理。

    • #{参数名/任意名}:取出参数值。
  • 多个参数:mybatis会做特殊处理。

    • 多个参数会被封装成 一个map,key:param1…paramN,或者参数的索引也可以 value:传入的参数值。
    • #{param1}:就是从map中获取指定的key的值。
    • 参数多时会封装map,为了不混乱,我们可以使用@Param来指定封装时使用的key;
    • (@Param(“id”)Integer id,@Param(“lastName”)String lastName)
    • #{id}就可以取出map中的值
  • POJO:如果多个参数正好是我们业务逻辑的数据模型,我们就可以直接传入pojo。

    • #{属性名}:取出传入的pojo的属性值。
  • Map:如果多个参数不是业务模型中的数据,没有对应的pojo,不经常使用,为了方便,我们也可以传入map。

    • #{key}:取出map中对应的值。
  • 特别注意:

    • 如果是Collection(List、Set)类型或者是数组,也会特殊处理。也是把传入的list或者数组封装在map中。
  • key:Collection(collection),如果是List还可以使用这个key(list)、数组(array)

    • public Employee getEmpById(List ids)
    • 取值:取出第一个id的值:#{list[0]}

  • #{}: 可以获取map中的值或者pojo对象属性的值;
  • ${}: 可以获取map中的值或者pojo对象属性的值;
  • select * from tbl_employee where id=${id} and last_name=#{lastName}
  • Preparing:select * from tbl_employee where id=2 and last_name=?
  • 区别:
    • #{}: 是以预编译的形式,将参数设置到sql语句中;PreparedStatement、防止sql注入。
    • ${}: 取出的值直接拼装在sql语句中;会有安全问题。原生jdbc不支持占位符的地方我们就可以使用 ${} 进行取值。

#{} 更丰富的用法

  • 规定参数的一些规则:
    • javaType、jdbcType、mode(存储过程)、numericScale、
    • resultMap、typeHandler、jdbcTypeName、expression(未来准备支持的功能);
  • jdbcType通常需要在某种特定的条件下被设置:
    • 在我们数据为null的时候,有些数据库可能不能识别mybatis对null的默认处理。比如Oracle(报错)
    • JdbcType OTHER:无效的类型;因为mybatis对所有的null都映射的是原生Jdbc的OTHER类型,oracle不能正确处理;
    • 由于全局配置中:jdbcTypeForNull=OTHER;oracle不支持;
    • 两种办法
      • 1、#{email,jdbcType=OTHER};
      • 2、jdbcTypeForNull=NULL
      • < setting name=”jdbcTypeForNull” value=”NULL”/>

  • 返回集合类型
  • resultType:如果返回的是一个集合,要写集合中元素的类型
1
2
3
4
5
public List<Employee> getEmpsByLastNameLike(String lastName);

<select id="getEmpsByLastNameLike" resultType="Employee">
select * from tbl_employee where last_name like #{lastName}
</select>


  • 返回Map类型
    • 返回单条记录的封装 Map
1
2
3
4
5
public Map<String, Object> getEmpByIdReturnMap(Integer id);

<select id="getEmpByIdReturnMap" resultType="map">
select * from tbl_employee where id=#{id}
</select>
  • 多条记录封装一个Map: Map<Integer,Employee>:键是这条记录的主键,值是记录封装后的javaBean
  • @MapKey: 告诉mybatis封装这个map的时候使用哪个属性作为map的key
1
2
3
4
5
6
@MapKey("lastName")
public Map<String, Employee> getEmpByLastNameLikeReturnMap(String lastName);

<select id="getEmpByLastNameLikeReturnMap" resultType="Employee">
select * from tbl_employee where last_name like #{lastName}
</select>


自定义 ResultMap,实现结果集映射

  • 自定义某个javaBean的封装规则
    • type:自定义规则的Java类型
    • id:唯一id方便引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<resultMap type="Employee" id="MySimpleEmp">

<!-- 指定主键列的封装规则 -->
<!-- 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>

<select id="getEmpById" resultMap="MySimpleEmp">
select * from tbl_employee where id=#{id}
</select>

一对一 resultMap

联合查询:级联属性封装结果集

  • 查询Employee的同时查询员工对应的部门
  • Employee —— Department
  • 一个员工有与之对应的部门信息
  • id 、last_name 、gender 、d_id 、did 、dept_name (private Department dept)
1
2
3
4
5
6
7
8
9
10
11
12
13
<resultMap type="Employee" id="MyDifEmp">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="did" property="dept.id"/>
<result column="dept_name" property="dept.departmentName"/>
</resultMap>

<select id="getEmpAndDept" resultMap="MyDifEmp">
SELECT e.id id,e.last_name last_name,e.gender gender,e.d_id d_id,
d.id did,d.dept_name dept_name FROM tbl_employee e,tbl_dept d
WHERE e.d_id=d.id AND e.id=#{id}
</select>

使用 association 定义关联的单个对象的封装规则

  • association可以指定联合的javaBean对象
  • property=”dept”:指定哪个属性是联合的对象
  • javaType:指定这个属性对象的类型[不能省略]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<resultMap type="Employee" id="MyDifEmp2">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>

<association property="dept" javaType="Department">
<id column="did" property="id"/>
<result column="dept_name" property="departmentName"/>
</association>
</resultMap>

<select id="getEmpAndDept" resultMap="MyDifEmp">
SELECT e.id id,e.last_name last_name,e.gender gender,e.d_id d_id,
d.id did,d.dept_name dept_name FROM tbl_employee e,tbl_dept d
WHERE e.d_id=d.id AND e.id=#{id}
</select>

使用association进行分步查询:

  • 1、先按照员工id查询员工信息。
  • 2、根据查询员工信息中的d_id值去部门表查出部门信息
  • 3、部门设置到员工中;
    • id 、last_name 、email 、gender 、d_id 、(private Department dept)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<resultMap type="Employee" id="MyEmpByStep">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>

<!-- association定义关联对象的封装规则 -->
<!--select:表明当前属性是调用select指定的方法查出的结果 -->
<!--column:指定将哪一列的值传给这个方法 -->
<!--流程:使用select指定的方法(传入column指定的这列参数的值)查出对象,并封装给property指定的属性 -->

<association property="dept" select="DepartmentMapper.getDeptById" column="d_id"></association>
</resultMap>

<!-- public Employee getEmpByIdStep(Integer id);-->
<select id="getEmpByIdStep" resultMap="MyEmpByStep">
select * from tbl_employee where id=#{id}
</select>

可以使用延迟加载懒加载、(按需加载)

  • 配置两个参数 即可
    1
    2
    3
    4
    5
    <settings>
    <!--显示的指定每个我们需要更改的配置的值,即使他是默认的。防止版本更新带来的问题 -->
    <setting name="lazyLoadingEnabled" value="true"/>//懒加载
    <setting name="aggressiveLazyLoading" value="false"/>// false 按需加载
    </settings>
1
2
3
4
5
public List<Employee> getEmpsByDeptId(Integer deptId);

<select id="getEmpByIdStep" resultMap="MyEmpByStep">
select * from tbl_employee where id=#{id}
</select>

一对多 resultMap

  • 查询部门的时候将部门对应的所有员工信息也查询出来
1
2
3
4
5
6
public class Department {
private Integer id;
private String departmentName;
private List<Employee> emps;

did dept_name || eid last_name email gender
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!--嵌套结果集的方式,使用collection标签定义关联的集合类型的属性封装规则  -->
<resultMap type="Department" id="MyDept">
<id column="did" property="id"/>
<result column="dept_name" property="departmentName"/>

<!--
collection定义关联集合类型的属性的封装规则
ofType:指定集合里面元素的类型
-->
<collection property="emps" ofType="Employee">
<!-- 定义这个集合中元素的封装规则 -->
<id column="eid" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
</collection>
</resultMap>

<!-- public Department getDeptByIdPlus(Integer id); -->
<select id="getDeptById" resultMap="MyDept">
SELECT d.id did,d.dept_name dept_name,
e.id eid,e.last_name last_name,e.email email,e.gender gender
FROM tbl_dept d
LEFT JOIN tbl_employee e
ON d.id=e.d_id
WHERE d.id=#{id}
</select>

collection:分步查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<resultMap type="Department" id="MyDeptStep">
<id column="id" property="id"/>
<id column="dept_name" property="departmentName"/>
<collection property="emps" select="DepartmentMapper.getEmpsByDeptId" column="{deptId=id}" fetchType="lazy"/>
</resultMap>

public Department getDeptByIdStep(Integer id);
<select id="getDeptByIdStep" resultMap="MyDeptStep">
select id,dept_name from tbl_dept where id=#{id}
</select>

public List<Employee> getEmpsByDeptId(Integer deptId);
<select id="getEmpsByDeptId" resultType="Employee">
select * from tbl_employee where d_id=#{deptId}
</select>

扩展:多列的值传递过去

  • 将多列的值封装map传递
    • column=”{key1=column1,key2=column2}”
    • fetchType=”lazy”:表示使用延迟加载;
      • lazy:延迟
      • eager:立即

< discriminator javaType=””> 鉴别器

  • 鉴别器:Mybatis可以使用discriminator判断某列的值,然后根据某列的值改变封装行为
    • 封装Employee:
      • 如果查出的是女生:就把部门信息查询出来,否则不查询;
      • 如果是男生,把last_name这一列的值赋值给email;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<resultMap type="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 column="gender" javaType="string">
<!--女生 resultType:指定封装的结果类型;不能缺少。或者 resultMap-->
<case value="0" resultType="Employee">
<association property="dept" select="DepartmentMapper.getDeptById" column="d_id">
</association>
</case>
<!--男生 ;如果是男生,把last_name这一列的值赋值给email; -->
<case value="1" resultType="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>


五、动态SQL

  • MyBatis 的强大特性之一便是它的动态 SQL。
    • if
    • trim (where, set)
    • choose (when, otherwise)
    • foreach


if

  • 查询员工,要求,携带了哪个字段查询条件就带上这个字段的值

  • test:判断表达式(OGNL)

    • if test=””:从参数中取值进行判断。
    • &&:&amp ;&amp ; 遇见特殊符号应该去写转义字符。
    • ”:&quot ;&quot ;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public List<Employee> getEmpsByConditionIf(Employee employee);

<select id="getEmpsByConditionIf" resultType="Employee">
select * from tbl_employee
<!-- where -->
<where>
<if test="id!=null">
id=#{id}
</if>
<if test="lastName!=null &amp;&amp; lastName!=&quot;&quot;">
and last_name like #{lastName}
</if>
<if test="email!=null and email.trim()!=&quot;&quot;">
and email=#{email}
</if>
<!-- OGNL 会进行字符串与数字的转换判断 "0"==0 -->
<if test="gender==0 or gender==1">
and gender=#{gender}
</if>
</where>
</select>


trim (where, set)

  • trim 标签体中是整个字符串拼串后的结果

  • and 写在条件后

    • prefix=””: 前缀 prefix给拼串后的整个字符串加一个前缀 。
    • prefixOverrides=””: 前缀覆盖 去掉整个字符串前面多余的字符。
    • suffix=””: 后缀 suffix给拼串后的整个字符串加一个后缀 。
    • suffixOverrides=””: 后缀覆盖 去掉整个字符串后面多余的字符。

where

  • 查询的时候如果某些条件没带可能sql拼装会有问题,比如第一个条件不符合会出现 where and
    • 1、给where后面加上1=1,以后的条件都and xxx
    • 2、Mybatis使用 where标签来将所有的查询条件包括在内。Mybatis 就会将 where 标签中拼装的sql,多出来的and或者or去掉。
      • where只会去掉第一个多出来的and或者or (建议 and 写在在条件前面),后面多出的and或者or where标签不能解决。

trim where (封装查询条件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 自定义字符串的截取规则 -->
public List<Employee> getEmpsByConditionTrim(Employee employee);

<trim prefix="where" suffixOverrides="and">
<if test="id!=null">
id=#{id} and
</if>
<if test="lastName!=null &amp;&amp; lastName!=&quot;&quot;">
last_name like #{lastName} and
</if>
<if test="email!=null and email.trim()!=&quot;&quot;">
email=#{email} and
</if>
<if test="gender==0 or gender==1">
gender=#{gender}
</if>
</trim>

set (封装修改条件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

public void updateEmp(Employee employee);

<update id="updateEmp">
<!-- Set标签的使用 -->
update tbl_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>

trim set (更新拼串)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void updateEmp(Employee employee);

<update id="updateEmp">
<!-- trim标签的使用 -->
update tbl_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>


choose (when, otherwise) 分支选择

  • 像带了break的swtich-case
  • 如果带了 id 就用 id 查,如果带了 lastName 就用 lastName 查;只会进入其中一个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public List<Employee> getEmpsByConditionChoose(Employee employee);

<select id="getEmpsByConditionChoose" resultType="Employee">
select * from tbl_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>


foreach

  • collection: 指定要遍历的集合。list类型的参数会特殊处理封装在map中,map的key就叫list
  • item: 将当前遍历出的元素赋值给指定的变量
  • separator: 每个元素之间的分隔符
  • open: 遍历出所有结果拼接一个开始的字符
  • close: 遍历出所有结果拼接一个结束的字符
  • index: 索引。
    • 遍历list的时候是index就是索引,item就是当前值。
    • 遍历map的时候index表示的就是map的key,item就是map的值。
  • #{变量名} 就能取出变量的值也就是当前遍历出的元素
1
2
3
4
5
6
7
8
public List<Employee> getEmpsByConditionForeach(List<Integer> ids);

<select id="getEmpsByConditionForeach" resultType="Employee">
select * from tbl_employee
<foreach collection="ids" item="item_id" separator="," open="where id in(" close=")">
#{item_id}
</foreach>
</select>


  • foreach批量保存

  • MySQL下批量保存:可以foreach遍历,Mysql支持values(),(),()语法。Oracle不支持values(),(),()语法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public void addEmps(@Param("emps")List<Employee> emps);

    <!-- <include refid="insertColumn"/> 引用外部定义sql片段 -->
    <insert id="addEmps">
    insert into tbl_employee(
    <include refid="insertColumn"></include>
    )
    values
    <foreach collection="emps" item="emp" separator=",">
    (#{emp.lastName},#{emp.email},#{emp.gender},#{emp.dept.id})
    </foreach>
    </insert>

  • 这种方式需要数据库连接属性 allowMultiQueries=true;
  • 这种分号分隔多个sql可以用于其他的批量操作(删除,修改)
    1
    2
    3
    4
    5
    6
    <insert id="addEmps">
    <foreach collection="emps" item="emp" separator=";">
    insert into tbl_employee(last_name,email,gender,d_id)
    values(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.dept.id})
    </foreach>
    </insert>


两个内置参数

  • 不只是方法传递过来的参数可以被用来判断,取值。
  • mybatis默认还有两个内置参数:
    • _parameter: 代表整个参数。
      • 单个参数:_parameter就是这个参数。
      • 多个参数:参数会被封装为一个map;_parameter 就是代表这个map。
    • _databaseId: 如果配置了databaseIdProvider标签。
      • _databaseId就是代表当前数据库的别名 oracle | mysql。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public List<Employee> getEmpsTestInnerParameter(Employee employee);

<select id="getEmpsTestInnerParameter" resultType="Employee">
<!-- bind:可以将OGNL表达式的值绑定到一个变量中,方便后来引用这个变量的值 -->
<bind name="_lastName" value="'%'+lastName+'%'"/>
<if test="_databaseId=='mysql'">
select * from tbl_employee
<if test="_parameter!=null">
where last_name like #{lastName}
</if>
</if>
<if test="_databaseId=='oracle'">
select * from employees
<if test="_parameter!=null">
where last_name like #{_parameter.lastName}
</if>
</if>
</select>

sql 标签

  • 抽取可重用的sql片段,方便后面引用

  • sql抽取 经常将要查询的列名,或者插入用的列名抽取出来方便引用

    • include 标签来引用已经抽取的sql:
      • id:唯一id方便引用;
    • include 还可以自定义一些property,sql标签内部就能使用自定义的属性
      • include-property:取值的正确方式 ${prop}
      • #{不能使用这种方式}
1
2
3
4
5
6
7
8
<sql id="insertColumn">
<if test="_databaseId=='mysql'">
last_name,email,gender,d_id
</if>
<if test="_databaseId=='oracle'">
employee_id,last_name,email,${prop}
</if>
</sql>

六、Mybatis 缓存

MyBatis 系统中默认定义了两级缓存。

一级缓存 (本地缓存)

  • 默认情况下,只有一级缓存( SqlSession级别的缓存 Map) 默认开启
  • 一级缓存失效情况(没有使用到当前一级缓存的情况,效果就是还需要再向数据库发出查询)
    • 1、sqlSession不同,一级缓存不同。
    • 2、sqlSession相同,查询条件不同。(当前一级缓存中还没有这个数据)
    • 3、sqlSession相同,两次查询之间执行了增删改操作(这次增删改可能对当前数据有影响)
    • 4、sqlSession相同,手动清除了一级缓存 openSession.clearCache();(缓存清空)


二级缓存 (全局缓存)

  • 二级缓存需要手动开启和配置,他是基于 namespace 级别的缓存。
  • 只有 XxxMapper.xml 使用了 < cache>< /cache>,才会开启二级缓存。

工作机制:

  • 1、一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中;
  • 2、开启二级缓存后。
  • 3、只有会话关闭;一级缓存中的数据会被保存到二级缓存中;新的会话查询信息,就可以参照二级缓存中的内容;
  • 4、不同 namespace 查出的数据会放在自己对应的缓存中(Map);
  • 一个 namespace 对应一个二级缓存。

使用:

  • 1、开启全局二级缓存配置:** < setting name=”cacheEnabled” value=”true”/>**
  • 2、去** mapper.xml** 中配置使用二级缓存:< cache>< /cache>
    • eviction: 缓存的回收策略
      • LRU – 最近最少使用的:移除最长时间不被使用的对象。
      • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
      • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
      • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
      • 默认的是 LRU。
    • flushInterval: 缓存刷新间隔 。缓存多长时间清空一次,默认不清空,设置一个毫秒值。
    • readOnly: 是否只读。
      • true:只读 Mybatis认为所有从缓存中获取数据的操作都是只读操作不会修改数据。
        • 为了加快获取速度,直接就将数据在缓存中的引用交给用户。不安全,速度快
      • false:非只读 Mybatis觉得获取的数据可能会被修改。
        • 会利用序列化 & 反序列的技术克隆一份新的数据给你。安全,速度慢。
    • size: 缓存存放多少元素。
    • type=””: 指定自定义缓存的全类名;实现 Cache 接口即可。
  • 3、 POJO需要实现序列化接口。

效果:

  • 数据会从二级缓存中获取 查出的数据都会被默认先放在一级缓存中。
  • 只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中。

和缓存有关的设置(属性):

  • cacheEnabled = true|false: 关闭缓存(二级缓存关闭)(一级缓存一直可用)
  • 每个select标签都有 useCache=”true”。 false:不使用缓存(一级缓存依然使用,二级缓存关闭)
  • 每个 增 删 改 标签都有:flushCache=”true”:
    • (一级二级都会清除)增删改执行完成后就会清除缓存;
    • 测试:flushCache=”true”:一级缓存清除;二级也会被清除;
    • 查询标签默认:flushCache=”false”: 如果查询标签 flushCache=true; 每次查询之后都会清空缓存;缓存是没有被使用的;
  • sqlSession.clearCache(); 只是清除 当前 Session 的一级缓存;
  • localCacheScope:本地缓存作用域(一级缓存SESSION); 当前会话的所有数据保存在会话缓存中;STATEMENT:可以禁用一级缓存;

为了提高扩展性。MyBatis定义了缓存接口Cache。可以通过实现Cache接口来自定义二级缓存。

整合 资源

  • 第三方缓存整合:
    • 导入第三方缓存包即可;
    • 导入与第三方缓存整合的适配包;
      • mybatis-ehcache-1.0.3.jar
      • ehcache-core-2.6.8.jar
      • slf4j-api-1.6.1.jar
      • slf4j-log4j12-1.6.2.jar
    • mapper.xml中使用自定义缓存 < cache type=”org.mybatis.caches.ehcache.EhcacheCache”>
  • ehcache.xml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <?xml version="1.0" encoding="UTF-8"?>
    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    <!-- 磁盘保存路径 -->
    <diskStore path="D:\ehcache" />

    <defaultCache
    maxElementsInMemory="10000"
    maxElementsOnDisk="10000000"
    eternal="false"
    overflowToDisk="true"
    timeToIdleSeconds="120"
    timeToLiveSeconds="120"
    diskExpiryThreadIntervalSeconds="120"
    memoryStoreEvictionPolicy="LRU">
    </defaultCache>
    </ehcache>

七、逆向工程 (MBG)

GitHub generator 文档

需要 Jar 包

  • mybatis-generator-core-1.3.7.jar
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.internal.DefaultShellCallback;

public class MyBatisTest {

public SqlSessionFactory getSqlSessionFactory() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}

@Test
public void testMbg() throws Exception {
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
File configFile = new File("mbg.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
callback, warnings);
myBatisGenerator.generate(null);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?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>
<context id="DB2Tables" targetRuntime="MyBatis3">
<!-- jdbcConnection:指定如何连接到目标数据库 -->
<jdbcConnection
driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis?allowMultiQueries=true"
userId="root"
password="root">
</jdbcConnection>

<!-- 默认 -->
<javaTypeResolver >
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>

<!--
javaModelGenerator:指定javaBean的生成策略
targetPackage="test.model":目标包名
targetProject="\MBGTestProject\src":目标工程
-->
<javaModelGenerator targetPackage="mybatis.bean" targetProject=".\src">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>

<!-- sqlMapGenerator:sql映射生成策略: -->
<sqlMapGenerator targetPackage="mybatis.dao" targetProject=".\conf">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>

<!-- javaClientGenerator:指定mapper接口所在的位置 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="mybatis.dao" targetProject=".\src">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>

<!-- 指定要逆向分析哪些表:根据表要创建javaBean -->
<table tableName="tbl_employee" domainObjectName="Employee"></table>
</context>
</generatorConfiguration>

按照条件查询(QBC查询)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
EmployeeExample example = new EmployeeExample();

//所有的 and 放在第一个 criteria 里面
Criteria criteria = example.createCriteria();
criteria.andLastNameLike("%e%");
criteria.andGenderEqualTo("1");

//需要 or 的放在第二个 criteria 里面
Criteria criteria2 = example.createCriteria();
criteria2.andEmailLike("%e%");

//最后 or 链接两个条件
example.or(criteria2);

List<Employee> list = mapper.selectByExample(example);
for (Employee employee : list) {
System.out.println(employee.getId());
}

八、Spring 整合 Mybatis

GitHub 整合示例 jpetstore-6
目的:

  • 1、Spring 管理所有组件。mapper的实现类。service==>Dao @Autowired:自动注入mapper;
  • 2、Spring 用来管理事务,spring声明式事务。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!--创建出SqlSessionFactory对象  -->
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<!-- configLocation指定全局配置文件的位置 -->
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<!--mapperLocations: 指定mapper文件的位置 如果不和接口包同名,需要配置-->
<property name="mapperLocations" value="classpath:mybatis/mapper/*.xml"></property>
</bean>

<!-- 扫描所有的mapper接口的实现,让这些mapper能够自动注入;base-package:指定mapper接口的包名 -->
<mybatis-spring:scan base-package="mybatis.dao"/>

<!-- 功能 同上
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="mybatis.dao"></property>
</bean>
-->

<!-- 配置一个可以进行批量执行的sqlSession -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean"></constructor-arg>
<constructor-arg name="executorType" value="BATCH"></constructor-arg>
</bean>


九、Mybatis 工作原理

分层框架

以后补充

插件开发

PageHelper

分页插件 使用方法

  • jar包

    • jsqlparser-0.9.5.jar
    • pagehelper-5.0.0-rc.jar
  • mybatis-config.xml 配置插件

    1
    2
    3
    <plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>

使用方法

  • Mapper接口方式的调用,推荐这种使用方式。
    1
    2
    3
    4
    5
    Page<Object> page = PageHelper.startPage(1, 5);
    List<Country> list = countryMapper.selectIf(1);

    page.xxx();
    关于个中 page 的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//list 查询的结果集; 第二个参数 传入连续显示几页
PageInfo info = new PageInfo(list,5);
//测试PageInfo全部属性
//PageInfo包含了非常全面的分页属性
info.getPageNum();
info.getPageSize();
info.getStartRow();
info.getEndRow();
info.getTotal();
info.getPages();
info.getFirstPage();
info.getLastPage();
info.isFirstPage();
info.isLastPage();
info.isHasPreviousPage();
info.isHasNextPage();

批量处理

  • 批量:(预编译sql (1次) ==>设置参数===>10000次===>执行(1次))
  • 非批量:(预编译sql=设置参数=执行)==> 10000 次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
public void testBatch() throws IOException{
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

//可以执行批量操作的sqlSession
SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
long start = System.currentTimeMillis();
try{
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
for (int i = 0; i < 10000; i++) {
mapper.addEmp(new Employee(UUID.randomUUID().toString().substring(0, 5), "b", "1"));
}
openSession.commit();
long end = System.currentTimeMillis();
System.out.println("执行时长:"+(end-start));
}finally{
openSession.close();
}

}
  • 在 Spring 中 设置 批量 sqlSession
    1
    2
    3
    4
    5
    <!-- 配置一个可以进行批量执行的sqlSession  -->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean"></constructor-arg>
    <constructor-arg name="executorType" value="BATCH"></constructor-arg>
    </bean>

感谢阅读


If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them. Thank you !