MyBatis MyBatis 是当前主流的 Java 持久层框架之一,它与 Hibernate 一样,也是一种 ORM 框架。因其性能优异,且具有高度的灵活性、可优化性和易于维护等特点,所以受到了广大互联网企业的青睐,是目前大型互联网项目的首选框架。
1.MyBatis核心组件介绍 MyBatis简介
MyBatis
是一个支持普通 SQL
查询、存储过程以及高级映射的持久层框架;使用简单的 XML
或注解进行配置和原始映射,用以将接口和 Java 的 POJO
(普通 Java 对象)映射成数据库中的记录,使得 Java 开发人员可以使用面向对象的编程思想来操作数据库。
MyBatis 工作原理
MyBatis
框架在操作数据库时,大体经过了8个步骤,如图1所示:
读取 MyBatis 配置文件 mybatis-config.xml。该文件配置了 MyBatis 的运行环境等信息(获取数据库连接);
加载映射文件 Mapper.xml。该文件配置了操作数据库的SQL语句。需在 mybatis-config.xml 中加载才能执行,可加载多个配置文件,每个配置文件对应数据库中的一张表;
构建会话工厂。通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory;
创建 SqlSession 对象。由会话工厂创建 SqlSession对 象,该对象中包含了执行SQL的所有方法;
MyBatis 底层定义了一个 Executor 接口来操作数据库,其根据 SqlSession 传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护;
在 Executor 接口的执行方法中,包含一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等。Mapper.xml 文件中一个 SQL 对应一个 MappedStatement 对象, SQL 的 id 即是 MappedStatement 的 id;
输入参数映射。在执行方法时,Executor执行器会通过 MappedStatement 对象在执行SQL前,将输入的 Java 对象映射到SQL语句中;
输出结果映射。在数据库中执行完 SQL 语句后,Executor 执行器会通过 MappedStatement 对象在执行 SQL 语句后,将输出结果映射至 Java 对象中;
MyBatis 核心对象
在使用 MyBatis 框架时,主要涉及两个核心对象 SqlSessionFactory
和 SqlSession
,它们在 MyBatis 框架中起着至关重要的作用。
SqlSessionFactory
它是单个数据库映射关系经过编译后的内存镜像,其主要作用是创建 SqlSession。SqlSessionFactoryBuilder 通过 XML 配置文件或一个预先定义好的 Configuration 实例来构建 SqlSessionFactory 实例。其实例是线程安全的。示例代码如下:
1 2 3 4 InputStream inputStream = Resources.getResourceAsStream("配置文件位置" );SqlSessionFactory ssf = new SqlSessionFactoryBuilder ().build(inputStream);
SqlSession
它是应用程序与持久层之间执行交互操作的一个单线程对象,其主要作用是执行持久化操作。SqlSession 对象包含了数据库中所有执行 SQL 操作的方法。其底层封装了 JDBC 连接,可直接使用其实例来执行已映射的 SQL 语句。其常用方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <T>T selectOne (String statement) ; <T>T selectOne (String statement,Object parameter) ; <E>List<E> selectList (String statment) ; <E>List<E> selectList (String statment, Object parameter) ; <E>List<E> selectList (String statment, Object parameter, RowBounds rowBounds) ;int insert (String statement) ;int insert (String statement, Object parameter) ;int update (String stetement) ;int update (String statement, Object parameter) ;int delete (String statement) ;int delete (String statement, Object prameter) ;
任务代码 TestMybatis.java
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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 package com.educode.test;import com.educode.model.Customer;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.springframework.jdbc.core.JdbcTemplate;import java.io.IOException;import java.io.InputStream;public class TestMybatis { public static void main (String[] args) throws IOException { init(); String resource = "mybatis-config.xml" ; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = null ; sqlSessionFactory = new SqlSessionFactoryBuilder ().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); Customer newCustomer = new Customer ("lisi" , "teacher" , "1888888888" ); int rows = sqlSession.insert("addCustomer" , newCustomer); if (rows > 0 ) { System.out.println("新增客户成功!" ); } Customer customer = null ; customer = sqlSession.selectOne("findCustomerByName" , "lisi" ); System.out.println(customer); sqlSession.commit(); sqlSession.close(); } public static void init () { ApplicationContext applicationContext = new ClassPathXmlApplicationContext ("applicationContext.xml" ); JdbcTemplate jdbcTemplate = (JdbcTemplate) applicationContext.getBean("jdbcTemplate" ); try { jdbcTemplate.execute("DROP TABLE customer" ); } catch (Exception e) { } String createCustomerSQL = "CREATE TABLE customer(" + "id INT PRIMARY KEY auto_increment," + "name VARCHAR(20)," + "jobs VARCHAR(50)," + "phone VARCHAR(50))" ; jdbcTemplate.execute(createCustomerSQL); } }
2.MyBatis配置文件 MyBatis 的核心配置文件中,包含了很多影响 MyBatis行为的重要信息。其中 configuration 元素是配置文件的根元素,其它元素都要在该元素内配置;
properties
该元素通常用于将内部的配置外在化,即通过外部的配置来动态地替换内部定义的属性。例如,数据库的连接等属性,就可以通过典型的 Java 属性文件中的配置来替换。示例如下:
1 2 // 引入 db.properties 配置文件中的属性<properties resource ="db.properties" />
settings
该元素主要用于改变 MyBatis 运行时的行为,例如开启二级缓存、开启延迟加载等。
设置参数
描述
有效值
cacheEnable
是否开启全局缓存
true|false(默认)
lazyLoadingEnable
是否开启全局延迟加载
true|false(默认)
multipleResultSetsEnable
是否允许单一语句返回多结果集
true(默认)|false
useColumnLabel
使用列标签代替列名
true(默认)|false
useGeneratedKeys
允许 JDBC 支持自动生成主键
true|false(默认)
defaultStatementTimeout
配置默认执行器
SIMPLE(默认)、REUSE(重用预处理语句)、BATCH(重用语句并执行批量执行)
mapUnderscoreToCamelCase
是否开启自动驼峰命名规则映射
true|false(默认)
jdbcTypeForNull
当参数没有指定 JDBC 类型时,设置默认 JDBC 类型
NULL、VARCHAR、OTHER(默认)
typeAliases
该元素用于为配置文件中的 Java 类型设置一个简短的名字,即设置别名。别名的设置与 XML 配置相关,其使用的意义在于减少全限定类名的冗余。
1 2 3 4 5 6 7 8 9 <typeAliases > <typeAlias alias ="user" type ="com.educode.model.User" /> <package name ="com.educode.model" /> </typeAliases >
可使用注解定义类型别名:
1 2 @Alias(value="user") public class User {...}
typeHandler
typeHandler 的作用是将预处理语句中传入的参数从 javaType(Java 类型)转换为 jdbcType(JDBC 类型),或者从数据库取出结果时将 jdbcType 转换为 javaType。
MyBatis 框架提供的常用类型处理器
类型处理器
java 类型
JDBC 类型
BooleanTypeHandle
java.lang.Boolean, boolean
数据库兼容的 BOOLEAN
ByteTypeHandle
java.lang.Byte, byte
数据库兼容的 NUMERIC 或 BYTE
ShortTypeHandle
java.lang.Short, short
数据库兼容的 NUMERIC 或 SHORT INTEGER
IntegerTypeHandle
java.lang.Integer, int
数据库兼容的 NUMERIC 或 INTEGER
LongTypeHandle
java.lang.Long, long
数据库兼容的 NUMERIC 或 LONG INTEGER
FloatTypeHandle
java.lang.Float, float
数据库兼容的 NUMERIC 或 FLOAT
DoubleTypeHandle
java.lang.Doubel, duoble
数据库兼容的 NUMERIC 或 DOUBLE
StringTypeHandle
java.lang.String
CHAR 或 VARCHAR
DateTypeHandle
java.util.Date
TIMESTAMP
可以通过自定义的方式对类型处理器进行扩展,typeHandler 元素用于在配置文件中注册自定义的类型处理器.
1 2 3 4 5 6 <typeHandlers > <typeHandler handler ="com.educode.handler.userTypeHandler" /> <packagename="com.educode.handler"/></typeHandlers >
objectFactory
MyBatis 框架每次创建结果对象的新实例时,都会使用一个对象工厂 ObjectFactory 的实例来完成。MyBatis 中默认的 ObjectFactory 的作用就是实例化目标类,它既可以通过默认构造方法实例化,也可以在参数映射存在的时候通过参数构造方法来实例化。
plugins
MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用,这种拦截调用是通过插件来实现的。plugins 元素的作用就是配置用户所开发的插件。
environments
该元素用于多种数据源配置,即配置多种数据库。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <environments default ="development" > <environment id ="development" > <transactionManager type ="JDBC" /> <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 > <environment id ="product" > ... </environment > </environments >
在 MyBatis 中,可以配置两种类型的事务管理器,分别是 JDBC 和 MANAGED。在 Spring + MyBatis 项目中,一般使用 Spring 自带的管理器来实现事务管理。
MyBatis 框架提供了 UNPOOLED 、POOLED 和 JNDI 三种数据源类型进行数据源配置。实际开发中,一般使用 POOLED 类型,此数据源利用池的概念将 JDBC 连接对象组织起来,避免了在创建新的连接实例时所需要初始化和认证的时间。这种方式使得并发 Web 应用可以快速地响应请求。配置属性说明如下:
属性
说明
poolMaximumActiveConnections
在任意时间可以存在的活动连接数,默认值:10
poolMaximumIdelConnections
任意时间可能存在的空闲连接数
poolMaximumCheckoutTime
在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000毫秒
poolTimeToWait
连接等待时间过长,重新尝试获取一个连接,默认值:20000毫秒
poolPingQuery
发送到数据库的侦测查询,用于检验连接是否正常工作
poolPingEnable
是否启用侦测查询,默认值:false
poolPingConnectionsNotUsedFor
配置 poolPingQuery 的使用频度。默认值:0
mappers
该元素用于指定 MyBatis 映射文件的位置,具体指定方法如下所示:
1 2 3 4 5 6 7 8 9 10 <mappers > <mapper resource ="com/educode/mapper/UserMapper.xml" /> <mapper url ="file:///D:/com/educode/mapper/UserMapper.xml" /> <mapper class ="com.educode.mapper.UserMapper" /> <package name ="com.educode.mapper" /> </mappers >
任务代码 mybatisConfig.xml
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 <?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 > <properties resource ="db.properties" /> <typeAliases > <package name ="com.educode.model" /> </typeAliases > <environments default ="mysql" > <environment id ="mysql" > <transactionManager type ="JDBC" /> <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 > </environments > <mappers > <mapper resource ="mapper/CustomerMapper.xml" /> </mappers > </configuration >
3.MyBatis的映射器 MyBatis 框架的强大之处就体现在映射文件的编写上。mapper 元素是映射文件的根元素,其他元素都是它的子元素。这些子元素及其作用如图2所示:select
该元素用于映射查询语句,示例如下:
1 2 3 <select id ="findCustomerById" parameterType ="Integer" resultType ="com.educode.model.Customer" > select * from t_customer where id=#{id}</select >
该查询语句的唯一标识为 findCustomerById,它接收一个 Integer 类型的参数,并返回一个 Customer 类型的对象。
select常用可配置属性
属性
说明
id
表示命名空间中的唯一标识,常与命名空间组合起来使用
parameterType
传入 SQL 语句的参数类的全限定名或者别名
resultType
从 SQL 语句中返回的类型的类的全限定名或者别名
resultMap
表示外部 resultMap 的命名引用
flushCache
执行 SQL 语句后,是否需要清空之前查询的本地缓存和二级缓存,默认值:false
userCache
是否开启二级缓存,默认值:false
timeout
设置超时参数,单位为秒。超时将抛出异常
fetchSize
获取记录的总条数设定,默认值:unset(依赖于驱动)
statementType
设置 MyBatis 工作 JDBC 的 Statement
resultSet
表示结果集的类型,默认值:unset(依赖于驱动)
insert
该元素用于映射插入语句,在执行完元素中定义的 SQL 语句后,会返回插入的记录数。该元素的属性与 select 元素的属性大部分相同,但还包含了3个特有属性.
属性
说明
keyProperty
将插入或更新操作时的返回值赋给指定返回类的某个属性,常设置为主键对应的属性
keyColumn
设置第几列是主键,当主键不是列表中第一列时需要设置
useGeneratedKeys
获取数据库内部生成的主键,如 MySQL 的自动递增字段,默认值:false
当执行插入操作后需要返回插入成功的数据生成的主键值时,可通过以上3个属性实现,示例如下:
1 2 3 <insert id ="addCustomer" parameterType ="com.educode.model.Customer" keyProperty ="id" useGeneratedKeys ="true" > insert into t_customer(name,jobs)values(#{username},#{jobs})</insert >
若使用数据库不支持主键自动增长或消除了主键自增的规则时,MyBatis 可自定义生成主键,示例如下:
1 2 3 4 5 6 7 <insert id ="insertCustomer" parameterType ="com.educode.model.Customer" > <selectKey keyProperty ="id" resultType ="Integer" order ="BEFORE" > select if(max(id) is null,1,max(id)+1) as newId from t_customer </selectKey > insert into t_customer(id,name,jobs)values(#{id},#{name},#{jobs})</insert >
其中 order 属性如果设置为 BEFORE,那么它会首先执行 selectKey 元素中的配置来设置主键,然后执行插入语句;如果设置为 AFTER,那么它会先执行插入语句,然后执行 selectKey 元素中的配置内容。
update、delete
update 和 delete 元素的属性基本与 select 元素中的属性一致。在执行完元素中定义的 SQL 语句后,会返回影响的记录数。示例如下:
1 2 3 4 5 6 7 8 <update id ="updateCustomer" parameterType ="com.educode.model.Customer" > update t_customer set name=#{name},jobs=#{jobs} where id=#{id}</update > <delete id ="deleteCustomer" parameterType ="Integer" > delete from t_customer where id=#{id}</delete >
sql
该元素的作用就是定义可重用的 SQL 代码片段,然后在其他语句中引用这一代码片段。示例如下:
1 2 3 4 5 6 <sql id ="customerColumns" > id,username,jobs,phone</sql > <select id ="findCustomerById" parameterType ="Integer" resultType ="com.educode.Customer" > select <include refid ="customerColumns" /> from t_customer where id=#{id}</select >
resultMap
该元素表示结果映射集。主要作用是定义映射规则、级联的更新以及定义类型转化器等。其包含的子元素及结构说明如下:
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 ="" id ="" > <constructor > <idArg /> <arg /> </constructor > <id /> <result /> <association property ="" /> <collection property ="" /> <discriminator javaType ="" > <casevalue=""/> </discriminator > </resultMap >
在默认情况下,MyBatis 程序在运行时会自动地将查询到的数据与需要返回的对象的属性进行匹配赋值(需要表中的列名与对象的属性名称完全一致)。然而实际开发时,数据表中的列和需要返回的对象的属性可能不会完全一致,这种情况下 MyBatis 是不会自动赋值的。此时,就可以使用 resultMap 元素进行处理。
任务代码 CustomerMapper.xml
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 <?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 ="mapper.CustomerMapper.xml" > <insert id ="addCustomer" parameterType ="customer" > insert into customer(name,jobs,phone) value(#{name},#{jobs},#{phone}) </insert > <select id ="findCustomerByName" resultType ="customer" parameterType ="java.lang.String" > select * from customer where name = #{name} </select > <sql id ="customerColumns" > id,name</sql > <insert id ="addCustomerKey" parameterType ="customer" keyProperty ="id" useGeneratedKeys ="true" > insert into customer(name,jobs)values(#{name},#{jobs}) </insert > <select id ="findCustomerById" parameterType ="Integer" resultType ="customer" > select <include refid ="customerColumns" /> from customer where id=#{id} </select > <update id ="updateCustomer" parameterType ="customer" > update customer set name=#{name},phone=#{phone},jobs=#{jobs} where id=#{id} </update > <delete id ="deleteCustomer" parameterType ="Integer" > delete from customer where id=#{id} </delete > </mapper >
4.动态SQL 开发人员在使用 JDBC 或其他类似的框架进行数据库开发时,通常都要根据需求去手动拼装 SQL,这样非常繁琐。而 MyBatis 提供的对 SQL 语句动态组装的功能很方便的完成 SQL 拼装。
MyBatis 采用了功能强大的基于 OGNL 的表达式来完成动态 SQL。其主要元素说明如下:
元素
说明
if
判断语句,用于单条件分支判断
choose(when、otherwise)
用于多条件分支判断,相当于的 java 中 switch
where、trim、set
辅助元素、用于处理一些 SQL 拼装、特殊字符问题
foreach
循环语句,常用语in语句等列举条件中
bind
创建一个变量,并绑定到上下文中,常用于模糊查询
if
该元素是最常用的判断语句,它类似于 Java 中的 if 语句,主要用于实现某些简单的条件选择。示例如下:
1 2 3 4 5 6 7 8 9 <select id ="findCustomer" parameterType ="com.educode.Customre" resultType ="com.educode.Customer" > select * form t_customer where 1=1 <if test ="name != null" > and name like = #{name} </if > <if test ="jobs != null" > and jobs like = #{jobs} </if > </select >
使用 if 元素的 test 属性分别对 name 和 jobs 进行了非空判断(test 属性多用于条件判断语句中,用于判断真假,大部分的场景中都是进行非空判断,有时候也需要判断字符串、数字和枚举等),如果传入的查询条件非空就进行动态 SQL 组装。
choose、when、otherwise
这些元素用于多条件分支判断,相当于的 java 中 switch 语句。示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <select id ="findCustomerWhen" resultType ="customer" parameterType ="customer" > select * from customer where 1 = 1 <choose > <when test ="name != null" > and name=#{name} </when > <when test ="jobs != null" > and jobs=#{jobs} </when > <otherwise > and phone is not null </otherwise > </choose > </select >
上述代码中,使用了 choose 元素进行 SQL 拼接,当第一个 when 元素中的条件为真,则只动态组装第一个 when 元素内的 SQL 片段,否则就继续向下判断第二个 when 元素中的条件是否为真,以此类推。当前面所有 when 元素中的条件都不为真时,则只组装 otherwise 元素内的 SQL 片段。
where、trim
where、trim 元素用于处理一些 SQL 拼装。如使用 where、trim 元素替换映射文件中的where 1=1
条件,示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <select id ="findCustomerWhere" resultType ="customer" parameterType ="customer" > select * from customer <where > <if test ="name != null" > and name=#{name} </if > <if test ="jobs != null" > and jobs=#{jobs} </if > </where > </select > <select id ="findCustomerTrim" resultType ="customer" parameterType ="customer" > select * from customer <trim prefix ="where" prefixOverrides ="and" > <if test ="name != null" > and name=#{name} </if > <if test ="jobs != null" > and jobs=#{jobs} </if > </trim > </select >
where元素会自动判断组合条件下拼装的 SQL 语句,只有 where 元素内的条件成立时,才会在拼接 SQL 中加入 where 关键字,否则将不会添加;即使 where 之后的内容有多余的AND或OR, where 元素也会自动将它们去除。
trim元素的作用是去除一些特殊的字符串,它的 prefix 属性代表的是语句的前缀(这里使用 where 来连接后面的 SQL 片段),而 prefixOverrides 属性代表的是需要去除的那些特殊字符串(这里定义了要去除 SQL 中的 and)。
set
该元素主要用于更新操作,其作用是在动态包含的 SQL 语句前输出一个 SET 关键字,并将 SQL 语句中最后一个多余的逗号去除。示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <update id ="updateCustomerSet" parameterType ="customer" > update customer <set > <if test ="name != null" > name=#{name}, </if > <if test ="jobs != null" > jobs=#{jobs}, </if > <if test ="phone != null" > phone=#{phone}, </if > </set > where id=#{id}</update >
上述代码中,使用了 set 和 if 元素相结合的方式来组装 update 语句。其中 set 元素会动态前置 SET 关键字,同时也会消除 SQL 语句中最后一个多余的逗号;if 元素用于判断相应的字段是否传入值,如果传入的更新字段非空,就将此字段进行动态 SQL 组装,并更新此字段,否则此字段不执行更新。
foreach
该元素用于数组和集合循环的遍历,通常在构建 IN 条件语句时使用,其使用方式如下:
1 2 3 4 5 6 7 <select id ="findCustomerByIds" resultType ="customer" parameterType ="List" > select * from customer where id in <foreach item ="id" index ="index" collection ="list" open ="(" separator ="," close =")" > #{id} </foreach > </select >
上述代码中,使用了 foreach 元素对传入的集合进行遍历并进行了动态 SQL 组装,其中使用属性说明如下:
属性
说明
item
配置的是当前循环的元素
index
配置的是当前元素在集合的位置下班
collection
配置的list是传递过来的参数类型(如array、list、Map集合的键)
open、close
配置的是以什么符号将这些集合元素包装起来。
separator
配置的是各个元素的间隔符
bind
该元素可以通过 OGNL 表达式来创建一个上下文变量。示例如下:
1 2 3 4 5 <select id ="findCustomerByPatternName" resultType ="customer" parameterType ="customer" > <bind name ="pattern_name" value ="'%'+ name +'%'" /> select * from customer where name like #{pattern_name}</select >
上述代码中,使用 bind 元素定义了一个 name 为 pattern_username 的变量, bind 元素中 value 的属性值就是拼接的查询字符串,其中_parameter.getUsername()
表示传递进来的参数(也可以直接写成对应的参数变量名,如 username)。在 SQL 语句中,直接引用 bind 元素的 name 属性值即可进行动态 SQL 组装。
任务代码 CustomerMapper.xml
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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 <?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 ="mapper.CustomerMapper.xml" > <insert id ="addCustomer" parameterType ="Customer" > insert into customer(name,jobs,phone) value(#{name},#{jobs},#{phone}) </insert > <select id ="findCustomerByName" resultType ="Customer" parameterType ="java.lang.String" > select * from customer where name = #{name} </select > <select id ="findCustomerIf" resultType ="Customer" parameterType ="Customer" > select * from customer where 1 = 1 <if test ="name != null" > and name=#{name} </if > <if test ="jobs != null" > and jobs=#{jobs} </if > </select > <select id ="findCustomerWhere" resultType ="Customer" parameterType ="Customer" > select * from customer <where > <if test ="name != null" > and name=#{name} </if > <if test ="jobs != null" > and jobs=#{jobs} </if > </where > </select > <select id ="findCustomerTrim" resultType ="Customer" parameterType ="Customer" > select * from customer <trim prefix ="where" prefixOverrides ="and" > <if test ="name != null" > and name=#{name} </if > <if test ="jobs != null" > and jobs=#{jobs} </if > </trim > </select > <select id ="findCustomerByIds" resultType ="Customer" parameterType ="List" > select * from customer where id in <foreach item ="id" index ="index" collection ="list" open ="(" separator ="," close =")" > #{id} </foreach > </select > <select id ="findCustomerByPatternName" resultType ="Customer" parameterType ="Customer" > <bind name ="pattern_name" value ="'%'+ name +'%'" /> select * from customer where name like #{pattern_name} </select > </mapper >
5.Mybatis的关联映射 针对多表之间的操作,MyBatis 提供了关联映射,可以很好地处理对象与对象之间的关联关系。
一对一
一对一的关系就是在本类中定义对方类型的对象,如 A 类中定义 B 类类型的属性 b,B 类中定义 A 类类型的属性 a。MyBatis 通过 resultMap 元素的子元素 association 元素来处理一对一关联关系,通常可以配置以下属性:
属性
说明
property
指定映射到的实体类对象属性,与表字段一一对应
column
指定表中对应的字段
javaType
指定映射到实体对象属性的类型
select
指定引入嵌套查询的子 SQL 语句,该属性用于关联映射中的嵌套查询
fetchType
指定在关联查询时是否启用延迟加载,属性值:lazy(默认)、eager
association 元素的使用可参考如下两种示例配置:
1 2 3 4 5 6 7 8 9 <association property ="idCard" javaType ="IdCard" select ="com.educode.mapperIdCardMapper.findCodeById" > </association > <association property ="idCard" javaType ="IdCard" > <id property ="id" column ="card_id" /> <result property ="code" column ="code" /> </association >
MyBatis 嵌套查询的方式要执行多条 SQL 语句,这对于大型数据集合和列表展示不是很好,因为这样可能会导致成百上千条关联的 SQL 语句被执行,从而极大地消耗数据库性能并且会降低查询效率。所以通常使用 MyBatis 提供的嵌套结果方式,来进行关联查询。
以个人和身份证之间的一对一关联关系为例,示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <select id ="findPersonCard" resultMap ="IdCardWithPersonResult" parameterType ="java.lang.Integer" > select p.*,card.code from person p,idcard card where p.card_id=card.id and p.id=#{id}</select > <resultMap id ="IdCardWithPersonResult" type ="Person" > <id property ="id" column ="id" /> <result property ="name" column ="name" /> <association property ="idCard" javaType ="IdCard" > <id property ="id" column ="card_id" /> <result property ="code" column ="code" /> </association > </resultMap >
一对多
一对多的关系就是一个 A 类类型对应多个 B 类类型的情况,需要在 A 类中以集合的方式引入 B 类类型的对象,在 B 类中定义 A 类类型的属性 a。
MyBatis 通过 resultMap 元素的子元素 collection 元素来处理一对多关联关系。其属性大部分与 association 元素相同,但其还包含一个特殊属性 ofType,ofType 属性与 javaType 属性对应,用于指定实体对象中集合类属性所包含的元素类型。
collection 元素的使用可参考如下两种示例配置:
1 2 3 4 5 6 7 8 9 <collection property ="ordersList" column ="id" ofType ="Orders" selett ="com.educode.mapper.OrdersMapper.selectOrders" > </collection > <collection property ="ordersList" ofType ="Orders" > <id property ="id" column ="orders_id" /> <result property ="number" column ="number" /> </collection >
以一个用户可以有多个订单为例,示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <select id ="findPersonOrders" resultMap ="OrdersWithPersonResult" parameterType ="java.lang.Integer" > select p.*,o.id as orders_id,o.number,o.person_id from person p,orders o where p.id=o.person_id and p.id=#{id}</select > <resultMap id ="OrdersWithPersonResult" type ="Person" > <id property ="id" column ="id" /> <result property ="name" column ="name" /> <collection property ="ordersList" ofType ="Orders" > <id property ="id" column ="orders_id" /> <result property ="personId" column ="person_id" /> <result property ="number" column ="number" /> </collection > </resultMap >
多对多
多对多的关系就是在A类中定义B类类型的集合,在 B 类中定义 A 类类型的集合。在实际项目开发中,多对多的关联关系也是非常常见的。
以订单和商品为例,一个订单可以包含多种商品,而一种商品又可以属于多个订单,订单和商品就属于多对多的关联关系。示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <select id ="findOrders" parameterType ="java.lang.Integer" resultMap ="OrdersWithProductResult" > select * from orders where id = #{id}</select > <resultMap id ="OrdersWithProductResult" type ="Orders" > <id property ="id" column ="id" /> <result property ="number" column ="number" /> <collection property ="productList" column ="id" ofType ="Product" select ="com.educode.mapper.ProductMapper.findProductById" > </collection > </resultMap > <select id ="findProductById" parameterType ="java.lang.Integer" resultType ="Product" > select * from product where id=(selete product_id from ordersitem where orders_id=#{id})</select >
上述代码中,使用嵌套查询的方式定义了一个 id 为 findOrdersWithPorduct 的 select 语句来查询订单及其关联的商品信息。在 resultMap 元素中使用了 collection 元素来映射多对多的关联关系,其中 property 属性表示订单持久化类中的商品属性,ofType 属性表示集合中的数据为 Product 类型,而 column 的属性值会作为参数执行 ProductMapper 中定义的 id 为 findProductById 的执行语句来查询订单中的商品信息。
定义了一个 id 为 findProductById 的执行语句,该执行语句中的 SQL 会根据订单 id 查询与该订单所关联的商品信息。由于订单和商品是多对多的关联关系,所以需要通过中间表 ordersitem 来查询商品信息。
任务代码 PersonMapper.xml
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 <?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 ="mapper.PersonMapper.xml" > <select id ="findPersonCard" resultMap ="IdCardWithPerson" parameterType ="java.lang.Integer" > select p.*,card.code from person p,idcard card where p.card_id=card.id and p.id=#{id} </select > <resultMap id ="IdCardWithPerson" type ="Person" > <id property ="id" column ="id" /> <result property ="name" column ="name" /> <association property ="idCard" javaType ="IdCard" > <id property ="id" column ="card_id" /> <result property ="code" column ="code" /> </association > </resultMap > <select id ="findPersonOrders" resultMap ="OrdersWithPersonResult" parameterType ="java.lang.Integer" > select p.*,o.id as orders_id,o.number,o.person_id from person p,orders o where p.id=o.person_id and p.id=#{id} </select > <resultMap id ="OrdersWithPersonResult" type ="Person" > <id property ="id" column ="id" /> <result property ="name" column ="name" /> <collection property ="ordersList" ofType ="Orders" > <id property ="id" column ="orders_id" /> <result property ="personId" column ="person_id" /> <result property ="number" column ="number" /> </collection > </resultMap > </mapper >
6.MyBatis的插件开发 Mybatis 插件也叫 Mybatis 拦截器,实际上它就是一个拦截器,通过应用代理模式,在方法级别上进行拦截。
它支持拦截以下方法:
执行器 Executor(update、query、commit、rollback 等方法);
参数处理器 ParameterHandler(getParameterObject、setParameters方法);
结果集处理器 ResultSetHandler(handleResultSets、handleOutputParameters 等方法); SQL
语法构建器 StatementHandler(prepare、parameterize、batch、update、query 等方法)。
拦截器接口介绍 MyBatis 插件需要实现拦截器接口 Interceptor (org.apache.ibatis.plugin Interceptor ) ,在实现类中对拦截对象和方法进行处理,该接口定义如下:
1 2 3 4 5 public interface Interceptor { Object intercept (Invocation invocation) throws Throwable; Object plugin (Object target) ; void setProperties (Properties properties) ; }
从上述代码可以看出,该接口有三个方法:
intercept 方法:该方法是 MyBatis 运行时要执行的拦截的方法,通过该方法的参数 invocation 可以得到很多有用的信息,该参数的 getTarget() 方法可以获取当前被拦截的对象,getMethod() 方法可以获取当前被拦截的方法,getArgs() 方法可以获取被拦截方法中的参数。通过调用 invocation.proceed() 方法可以执行被拦截对象真正的方法;
plugin 方法:target 是被拦截的对象,它的作用是给被拦截对象生成一个代理对象,只需要调用 Mybatis 提供的 Plugin 类的 wrap 静态方法就可以通过 Java 的动态代理拦截目标对象;
setProperties 方法:允许在 plugin 元素中配置所需参数,该方法在插件初始化的时候会被调用一次。
拦截器签名
除了需要实现拦截器接口外,还需要给实现类配置以下拦截器注解。 比如想拦截 StatementHandler 对象的 prepare 方法,该方法有一个参数Connection 对象,可以这样声明:
1 2 3 4 5 6 7 8 @Intercepts({ @Signature( type =StatementHandler.class, method="prepare" , args={Connection.class})}) public class MyPlugin implements Interceptor { ...... }
上述代码中,@Signature
注解包含以下三个属性:
type:设置拦截的接口;
method:设置拦截接口中的方法名;
args:设置拦截方法的参数类型数组,通过方法名和参数类型可以确定唯一一个方法。
自定义插件示例
编写类 MyInterceptor 实现拦截器接口 Interceptor,设置拦截器签名:
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 package com.yy;import org.apache.ibatis.executor.statement.StatementHandler;import org.apache.ibatis.plugin.*;import org.apache.ibatis.reflection.MetaObject;import org.apache.ibatis.reflection.SystemMetaObject;import java.util.Properties;@Intercepts(@Signature(type = StatementHandler.class,method = "parameterize",args=java.sql.Statement.class)) public class MyInterceptor implements Interceptor { @Override public Object intercept (Invocation invocation) throws Throwable { Object target = invocation.getTarget(); MetaObject metaObject = SystemMetaObject.forObject(target); Object value = metaObject.getValue("parameterHandler.parameterObject" ); metaObject.setValue("parameterHandler.parameterObject" , 3 ); Object proceed = invocation.proceed(); return proceed; } @Override public Object plugin (Object o) { Object wrap = Plugin.wrap(o, this ); return wrap; } @Override public void setProperties (Properties properties) { } }
上述代码中,插件签名用于拦截 StatementHandler 对象的 parameterize方法,该插件拦截器可以实现将参数值改为 3。
在 mybatis 的全局配置文件配置我们自定义的插件:
1 2 3 4 <plugins> <plugin interceptor="com.yy.MyInterceptor" > </plugin> </plugins>
配置完自定义插件后,当我们执行我们的 mybatis 代码时,会自动加载该插件,执行拦截任务。
任务代码 MyInterceptor.java
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 package com.yy;import org.apache.ibatis.executor.statement.StatementHandler;import org.apache.ibatis.plugin.*;import org.apache.ibatis.reflection.MetaObject;import org.apache.ibatis.reflection.SystemMetaObject;import java.util.Properties;@Intercepts(@Signature(type = StatementHandler.class,method = "parameterize",args=java.sql.Statement.class)) public class MyInterceptor implements Interceptor { @Override public Object intercept (Invocation invocation) throws Throwable { Object target = invocation.getTarget(); MetaObject metaObject = SystemMetaObject.forObject(target); Object value = metaObject.getValue("parameterHandler.parameterObject" ); int id=Integer.parseInt(String.valueOf(value)); metaObject.setValue("parameterHandler.parameterObject" , id+1 ); Object proceed = invocation.proceed(); return proceed; } @Override public Object plugin (Object o) { Object wrap = Plugin.wrap(o, this ); return wrap; } @Override public void setProperties (Properties properties) { } }
mybatis_config.xml
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 <?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 > <plugins > <plugin interceptor ="com.yy.MyInterceptor" > </plugin > </plugins > <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/mydb?useUnicode=true& characterEncoding=utf-8" /> <property name ="username" value ="root" /> <property name ="password" value ="123123" /> </dataSource > </environment > </environments > <mappers > <mapper resource ="UserMapper.xml" /> </mappers > </configuration >
MyBatis的解析和运行原理 1.构建SqlSessionFactory过程 MyBatis运行过程 上图中各个对象的解释如下:
SqlSessionFactory 创建
SqlSessionFactory 是 MyBatis 的核心类之一,它最重要的功能就是提供 MyBatis 的核心接口 SqlSession。MyBatis 使用了 Builder 模式去创建 SqlSessionFactory,在开发中我们可以通过 SqlSessionFactoryBuilder 去构建,如下所示:
1 2 3 4 Reader reader = Resources.getResourceAsReader("mybatis_config.xml" );SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder ().build(reader);
上述构建步骤分为两步,第一步使通过 org.apache.ibatis builder.xml.XMLConfigBuilder 解析配置的 XML 文件,读出所配置的参数,并将读取的内容存入 org.apache.ibatis.session.Configuration 类对象中。
第二步使用 Confinguration 对象去创建 SqlSessionFactory。SqlSessionFactory 是一个接口,而不是一个实现类,为此 MyBatis 提供了一个默认的实现类org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
。在大部分情况下都没有必要自己去创建新 SqlSessionFactory 实现类。
Configuration的作用
SqlSessionFactory 构建中,Configuration 是最重要的,它的作用是:
读入配置文件,包括基础配置的 XML 和映射器 XML(或注解〉。
初始化一些基础配置,比如 MyBatis 的别名等,一些重要的类对象(比如插件、映射器、Object 工厂、typeHandlers 对象等)。
提供单例,为后续创建 SessionFactory 服务,提供配置的参数。
执行一些重要对象的初始化方法。
任务
2.SqlSession运行过程 获取SqlSession对象
获取 SqlSession 对象我们用的是以下代码:
1 sqlSession = sqlSessionFactory.openSession();
上述代码执行后,SqlSessionFactory 的 openSession 方法会获取 SqlSession 接口的实现类 DefaultSqlSession 对象。
有了 DefaultSqlSession 对象,我们以查询一条数据为例,讲解一下整个处理过程:
1 2 3 4 5 6 7 SqlSession session = sqlSessionFactory.openSession();try { BlogMapper mapper = session.getMapper(BlogMapper.class); Blog blog = mapper.selectBlog(101 ); } finally { session.close(); }
上述代码中,我们首先获取了 SqlSession 对象,然后通过该对象调用 Mapper 接口中的 selectBlog 方法实现对数据库的操作。
通过 DefaultSqlSession 对象的 getMapper 方法获取的是一个 MapperProxy 代理对象,这也是 Mapper 接口不用实现类的原因。当调用 BlogMapper 中的方法时,由于 BlogMapper 是一个 JDK 动态代理对象,它会运行 invoke 方法。
invoke 方法会判断代理的对象是否是一个类,由于代理对象是一个接口,所以通过 cachedMapperMethod 生成一个 MappedMethod 对象,然后执行 execute 方法。
SqlSession 下的四大对象
映射器就是一个动态代理对进入到了 MapperMethod 的 execute 方法,然后它经过简单地判断就进入了 SqlSession 的 delete、update、insert、select 等方法,那么这些方法是如何执行呢? 实际上 SqlSession 是通过 Executor、StatementHandle、ParamterHandler、ResultSetHandler 来完成数据库操作和结果返回的。
Executor 代表执行器,由它调度 StatementHandler、ParameterHandler、 ResultSetHandler 等来执行对应的 SQL 。其中 StatementHandler 是最重要;
StatementHandler 的作用是使用数据库 Statement ( PreparedStatement) 执行操作,它是四大对象的核心,起到承上启下的作用,许多重要的插件都是通过拦截它来实现的;
ParameterHandle 是用来处理 SQL 参数的;
ResultSetHandl 是进行数据集 (ResultSet) 封装返回处理的。
执行过程如下:
SqlSession 是通过执行器 Executor 调度 StatementHandler 来运行的,而 StatementHandler 经过以下 3 步:
prepared 预编译 SQL;
parameterize 设置参数;
query/update 执行 SQL。
其中,parameterize 是调用 parameterHandler 的方法设置的,而参数是根据类型处理器 typeHandler 处理的。query/update 方法通过 ResultSetHandler 进行处理结果的封装,如果是 update 语旬,就返回整数,否则就通过 typeHandler 处理结果类型,然后用 ObjectFactory 提供的规则组装对象,返回给调用者。
任务
MyBatis: 1-5
MyBatis的插件开发:6
MyBatis 的解析和运行原理:1-2