MyBatis是支持定制化SQL、存储过程以及高级映射的优秀持久层框架。MyBatis几乎避免了所有的JDBC代码和手动设置参数,MyBatis可以对配置使用简单的XML或注解,将接口和Java的POJOs(普通Java对象)映射成 数据库中的记录。
一、添加依赖
如果使用Maven来构建项目,则需要引入如下两个依赖:
<!-- 引入mabatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.3.1</version>
</dependency>
<!-- 引入JDBC -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.41</version>
</dependency>
二、properties属性文件(config.properties)
JDBC连接需要的driver、url以及用户信息等配置可以在Java属性文件中配置,config.properties文件配置如下:
driver=com.mysql.jdbc.Driver
## format -> jdbc:mysql://<host>:<port>/<database_name>?property1=value1&property2=value2
url=jdbc:mysql://localhost:3306/test
username=root
password=123
在MyBatis的XML配置文件中可以通过properties标签引入,引入后属性文件定义的值可以通过${key}方式在XML文件中引用。
<configuration>
...
<properties resource="config.properties">
<property name="username" value="root" />
<property name="password" value="456" />
</properties>
...
</configuration>
注意,properties标签除了通过resource引入外部properties文件的属性外,也可通过property标签定义属性。对于同名key,外部资源文件优先级高于property标签。
三、XML配置文件 (config.xml)
本文使用的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>
<properties resource="config.properties">
<property name="username" value="root" />
<property name="password" value="456" />
</properties>
<typeAliases>
<typeAlias alias="User" type="com.xiaofan.test.User" />
</typeAliases>
<environments default="development"> <!-- 默认环境ID,如:default="developent" -->
<environment id="development"> <!-- 每个environment元素定义的环境ID, 如:id="developent" -->
<transactionManager type="JDBC"/> <!-- 事务管理器的配置,如:type="JDBC" -->
<dataSource type="POOLED"> <!-- 数据源配置,如:type="POOLED" -->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="user_mapper.xml"/>
</mappers>
</configuration>
首先,MyBatis的配置文件顶层结构如下:
- configuration - properties - settings - typeAliases - typeHandlers - objectFactory - plugins - environments - environment - transactionManager - dataSource - databaseIdProvider - mapper
properties
见上节
settings
settings标签是MyBatis中重要的参数调整设置标签,可通过设置其属性改变MyBatis的运行时行为。本文均采用MyBatis默认设置,详细参数设置见文档
typeAliases
类别名是为Java类设置一个短的名字,它存在的意义仅在用于减少类完全限定名的冗余。如下:
<configuration>
...
<typeAliases>
<typeAlias alias="User" type="com.xiaofan.test.User" />
</typeAliases>
...
</configuration>
在mapper.xml文件中可将com.xiaofan.test.User全路径简写。如下:
<mapper namespace="com.xiaofan.test.UserDAO">
...
<!-- resultType="com.xiaofan.test.User" -->
<select id="findByName" resultType="User">
select * from user where name=#{name}
</select>
...
</mapper>
typeHandlers
无论是MyBatis在预处理中设置一个参数,还是从结果集中取一个值,都会用类型处理器将获取的值以合适的方式转换成Java类型,部分常用的默认处理器如下:
处理器 | JDBC类型 | Java类型 |
---|---|---|
BooleanTypeHandler | BOOLEAN | java.lang.Boolean, boolean |
IntegerTypeHandler | NUMERIC, INTEGER | java.lang.Integer, int |
LongTypeHandler | NUMERIC, LONG INTEGER | java.lang.Long, long |
FloatTypeHandler | NUMERIC, DOUBLE | java.lang.Double, double |
StringTypeHandler | CHAR, VARCHAR | java.lang.String |
DateTypeHandler | TIMESTAMP | java.util.Date |
除了以上默认类型处理器件外,也可以通过实现org.apache.ibatis.type.TypeHandler或继承org.apache.ibatis.type.BaseTypeHandler来重写或创建自己的类型处理器。配置文件如下
<configuration>
...
<typeHandlers>
<typeHandler handler="your handler path" />
</typeHandlers>
...
</configuration>
objectFactory
MyBatis每次创建结构对象的新实例时,都会使用一个对象工厂(objectFactory)实例来完成,默认的对象工厂仅仅是实例化目标类,如果参数映射不存在,则通过默认构造方法完成实例化,如果存在参数映射,则通过参数构造方法完成实例化。也可以通过创建自己的对象工厂完成实例化。
plugins
MyBatis允许在已映射语句执行过程中的某一点进行拦截,默认情况下,MyBatis允许使用插件来拦截的方法包括:Executor、ParameterHandler、ResultSetHandler、StatementHandler。
environments
MyBatis可以配置成适应多种环境,这种机制有助于将SQL映射用于多种数据库,每个环境需要创建一个独立的SqlSessionFactory实例,每个数据库对应一个。一份MyBatis环境配置示例如下:
<configuration>
...
<environments default="development"> <!-- 默认环境ID,如:default="developent" -->
<environment id="development"> <!-- 每个environment元素定义的环境ID, 如:id="developent" -->
<transactionManager type="JDBC"/> <!-- 事务管理器的配置,如:type="JDBC" -->
<dataSource type="POOLED"> <!-- 数据源配置,如:type="POOLED" -->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
...
<configuration>
MyBatis中有两种类型的事务管理器(type=”[JDBC|MANAGED]”)。
JDBC:使用JDBC的提交和回滚设置,它依赖于数据源得到的连接来管理事务作用域;
MANAGED:它不提交或回滚一个连接,而是让容器来管理事务的整个生命周期。如果使用spring+MyBatis,则没必要配置事务管理器,因为Spring会使用自带的管理器覆盖前面的设置。
MyBatis有三种内建的数据源类型(type=”[UNPOOLED|POOLED|JNDI]”)。
UNPOOLED:顾名思义,该数据源类型将不使用连接池,每次被请求时打开和关闭连接。缺点时慢,它对没有性能要求的简单应用程序中是个很好的选择。它的必要属性:
driver:JDBC驱动的Java类完全限定名
url:数据库JDBC 的URL地址
username:登录数据库的用户名
password:登录数据库的密码
defaultTransactionIsolationLevel:默认的连接事务隔离级别
POOLED:该类型利用数据库连接池,避免了创建新数据库连接时需要的初始化和认证时间。这是兵法Web应用快速响应请求的流行处理方式。
poolMaximumActiveConnections:任意时间可存在的连接数,默认:10
poolMaximumIdleConnections:任意时间可存在的空闲连接数
poolMaximumCheckoutTime:被强制返回前,池中连接被checkout时间,默认:20秒
poolPingQuery:发送侦测查询到数据库。默认:NO PING QUERY SET
poolPingEnabled:是否启用侦测查询。默认:false
poolPingConnectionsNotUsedFor:poolPingQuery的使用频度。默认:0
JNDI:该数类型的实现是为了能在如EJB或应用服务器这类容器中使用,容器可集中或在外部配置数据源,然后放置在一个JNDI上下文中。
databaseIdProvider
MyBatis可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的databaseId属性。
mappers
mappers标签是让MyBatis查找映射文件,从而能够获取到SQL映射语句。其中映射文件的路径可以是资源文件路径的相对路径,也可以是绝对路径。使用范例如下
<configuration>
...
<mappers>
<mapper resource="user_mapper.xml"/> <!-- resource相对路径-->
</mappers>
...
</configuration>
注意,properties、setting、typeAliases等configuration子标签必须按以上顺序设置,顺序设置错误会导致如下错误:
org.xml.sax.SAXParseException; ... 元素类型为 "configuration" 的内容必须匹配 "(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,plugins?,environments?,databaseIdProvider?,mappers?)"。
四、XML映射文件 (user_mapper.xml)
MyBatis的SQL映射功能强大,它能极大的简化SQL构建。SQL映射文件的顶级元素如下:
cache : 给定命名空间的缓存配置
cache-ref : 其他命名空间缓存配置的引用
resultMap : 描述如何从数据库结果集中加载对象
sql : 可以被其他语句引用的可重用语句块
insert : 插入语句 update : 更新语句 delete : 删除语句 select : 查询语句
本文demo使用的一个简单的MyBatis 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 namespace="com.xiaofan.test.UserDAO">
<resultMap id="UserResultMap" type="User">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
</resultMap>
<sql id="tmpSql" >
select * from ${realdb}
</sql>
<insert id="insert" parameterType="User" useGeneratedKeys="true" keyProperty="id">
insert into user(name,age) values(#{name},#{age})
</insert>
<insert id="batchInsert" useGeneratedKeys="true" keyProperty="id">
insert into user(name, age) values
<foreach item="item" collection="list" separator=",">
(#{item.name}, #{item.age})
</foreach>
</insert>
<update id="update" parameterType="User">
update user set name=#{name},age=#{age} where name=#{name}
</update>
<delete id="delete" parameterType="String">
delete from user where name=#{name}
</delete>
<select id="findByName" resultMap="UserResultMap">
<include refid="tmpSql">
<property name="realdb" value="user" />
</include>
where name=#{name}
</select>
<select id="selectAll" resultType="User">
select * from user
</select>
</mapper>
insert/update/delete/select
以上配置文件中的select、insert、update、delete元素没有使用太多属性,其实,MyBatis为CRUD元素提供了丰富的属性可选。部分常用属性配置如下
属性 | 描述 | 默认值 |
---|---|---|
id | 在命名空间中唯一的标识符,可用该标识来引用这条语句 | |
parameterType | [可选] 当前语句参数类的完全限定名或别名,MyBatis可通过TypeHandler推断 | unset |
resultType | 返回的期望类型类的完全限定名或别名。注意:如果是集合,则resultType是集合包含的类型,如List中的E | |
resultMap | 引用外部定义的resultMap,如果是集合情况如resultMap。resultType和resultMap不可同时使用 | |
timeout | 驱动程序等待数据库返回的时间(秒),超过时间抛出异常 | unset |
useGeneratedKeys | (insert/update)允许MyBatis使用JDBC的getGeneratedKeys方法取出数据库内部生成的主键 | false |
keyProperty | (insert/update)MyBatis通过getGeneratedKeys的返回值或insert语句的selectKey子元素设置它的键值 | unset |
sql
sql标签可以用来定义可重用的SQL代码片段,可以包含在其他语句中。它可以被静态地参数化(可不指定),不同的实例拥有不同的属性值。一份sql可重用片段如下:
...
<sql id="tmpSql" >
select * from ${realdb}
</sql>
...
<select id="findByName" resultMap="UserResultMap">
<include refid="tmpSql">
<property name="realdb" value="user" />
</include>
where name=#{name}
</select>
...
resultmap
resultmap元素可以避免像JDBC那样需要从结果集中去处数据的代码。如下情况MyBatis会使用Javabean User来作为领域模型,MyBatis会默认在幕后自动创建一个ResutlMap,基于属性名来映射到JavaBean的属性上,如果列名没有精确匹配,也可以通过select as来指定别名来匹配, 如果User有nameAlias属性,则该属性会被赋name的返回值。
...
<select id="findByName" resultType="com.xiaofan.test.User">
select id, name as nameAlias, age from user where name=#{name}
</select>
...
resultmap属性说明
resultmap
- constructor : 构造方法注入,可不用暴露公共方法
- id : 标记结果作为ID
- result : 注入到字段或JavaBean属性的普通结果
- association : 关联嵌套结果
- collection : 集合嵌套结果
- discriminator : 鉴别器,作用类似Java的Switch
构造方法注入可在初始化时为类设置属性的值,不用暴露公共的方法,resultmap的constructor属性用法如下:
User类:
public class User {
...
User(Long id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
...
}
xml映射文件:
<mapper namespace="com.xiaofan.test.UserDAO">
...
<resultMap id="UserResultMap" type="User">
<constructor>
<idArg column="id" javaType="long" />
<arg column="name" javaType="String" />
<arg column="age" javaType="int" />
</constructor>
</resultMap>
<select id="findByName" resultMap="UserResultMap">
select * from user where name=#{name}
</select>
...
</mapper>
五、动态SQL
MyBatis的强大特性之一是它的动态SQL,它允许你根据不同条件拼接不同的SQL语句。MyBatis提供的动态SQL元素如下
- if - choose(when, otherwise) - trim(where, set) - foreach
if
if基本使用方法如下, 该语句可动态选择是否加入age筛选条件。
...
<select id="findByName" resultMap="UserResultMap">
select * from user where name=#{name}
<if test="age != null" >
and age = #{age}
</if>
</select>
...
choose(when, otherwise)
当只想从条件中选择其中一个时,可以使用choose元素,类似Java中的Switch,多个条件匹配时返回第一个匹配的条件。使用范如下:
...
<select id="findByName" resultMap="UserResultMap">
select * from user where name = #{name}
<choose>
<when test="id != null">
and id = #{id}
</when>
<otherwise>
and age = #{age}
</otherwise>
</choose>
</select>
...
trim(where, set)
trim或where、set元素是用来解决条件元素匹配带来的多余连接符。其中where使用范例如下,他会自动去掉多余的and或or链接符号
...
<select id="findByName" resultMap="UserResultMap">
select * from user
<where>
<if test="name != null">
and name = #{name}
</if>
<if test="age != null">
and age = #{age}
</if>
</where>
</select>
...
等同于:
...
<select id="findByName" resultMap="UserResultMap">
select * from user
<trim prefix="where" prefixOverrides="and | or" >
<if test="name != null">
and name = #{name}
</if>
<if test="age != null">
and age = #{age}
</if>
</trim>
</select>
...
set使用范例如下:
...
<update id="update" parameterType="User">
update user
<set>
<if test="name != null">
name = #{name},
</if>
<if test="age != null">
age =#{age},
</if>
</set>
where id = #{id}
</update>
...
等同于:
...
<update id="update" parameterType="User">
update user
<trim prefix="set" suffixOverrides=",">
<if test="name != null">
name = #{name},
</if>
<if test="age != null">
age =#{age},
</if>
</trim>
where id = #{id}
</update>
...
foreach
foreach元素允许指定一个集合,并对该集合进行遍历。当使用可迭代对象或者数组时,index时但前迭代的次数,item时本次迭代的元素;当使用Map.Entry时,index是键,item是值。
...
<insert id="batchInsert" useGeneratedKeys="true" keyProperty="id">
insert into user(name, age) values
<foreach item="item" index="idx" collection="list" separator="," >
(#{item.name}, #{item.age})
</foreach>
</insert>
...
六、JAVA API使用
MyBatis提供的主要Java接口是SqlSession,可以通过这个接口执行命令、获取映射器和管理事务。SqlSession是由SqlSessionFactory实例创建,SqlSessionFactory实例包含创建SqlSession实例的所有方法,SqlSessionFactory是由SqlSessionFactoryBuilder创建,SqlSessionFactoryBuilder可以通过XML配置来创建SqlSessionFactory。一个完整的调用示例如下:
public class Test {
public static void main(String [] args) {
try {
String resource = "config.xml";
Reader reader = Resources.getResourceAsReader(resource);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(reader);
SqlSession session = factory.openSession();
UserDAO userDAO = session.getMapper(UserDAO.class);
userDAO.batchInsert(Lists.<User>newArrayList(new User(null, "test1", 11), new User(null, "test2", 12)));
session.commit();
User user = userDAO.findByName(new User(null, "test1", null));
System.out.println(JSON.toJSONString(user));
} catch (Exception e) {
e.printStackTrace();
}
}
}
配置文件读取
默认情况下,MyBatis加载资源文件的默认路径是src/main, 如果需要指定其他路径,需在项目POM文件中指定资源文件路径。以上config.xml文件添加在resources资源文件夹下,因此需要将resources文件夹添加微资源加载路径,以保证config.xml文件能够被正确加载。
...
<build>
<finalName>xiaofantest</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>*.*</include>
</includes>
</resource>
</resources>
</build>
...
SqlSessionFactory
SqlSessionFactory有六个方法可以创建SqlSession,其中使用较多的有两个,如下。默认openSession()方法没有参数,创建的SqlSession会开启一个事务,该事务需要用户自己提交。如果将创建参数autoCommit设置为‘true’,SqlSession则会开启自动提交功能。
SqlSession openSession()
SqlSession openSession(boolean autoCommit)
SqlSession
SqlSession有超过20个方法,这些方法呗用来执行定义在SQL XML映射文件中的select、insert、update、delete语句,每一条语句都适用语句的ID属性和参数对象,参数可以是原声类型、JavaBean、POJO或Map。常用的方法如下:
// 带参数的方法
<T> T selectOne(String statement, Object parameter)
<E> List<E> selectList(String statement, Object parameter)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey)
int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)
// 不带参数的方法
<T> T selectOne(String statement)
<E> List<E> selectList(String statement)
<K,V> Map<K,V> selectMap(String statement, String mapKey)
int insert(String statement)
int update(String statement)
int delete(String statement)
// 查询方法的高级版本
<E> List<E> selectList (String statement, Object parameter, RowBounds rowBounds)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowbounds)
void select (String statement, Object parameter, ResultHandler<T> handler)
void select (String statement, Object parameter, RowBounds rowBounds, ResultHandler<T> handler)
对于如下一个sql 映射语句配置,有两种执行该sql的方式。一种是映射语句mapper的namespace+statementId; 另一种是指定mapper接口。
...
<select id="findByName" resultMap="UserResultMap">
select * from user
<trim prefix="where" prefixOverrides="and | or" >
<if test="name != null">
and name = #{name}
</if>
<if test="age != null">
and age = #{age}
</if>
</trim>
</select>
...
第一种方式示例:一个带参数的简单查询语句如下:
...
User user = session.selectOne("com.xiaofan.test.UserDAO.findByName", new User(null, "Jerry", null));
...
第二种方式示例:指定mapper(映射器)对应的接口,每个映射器方法签名应该没有关联的字符串参数ID,但方法名必须与sql映射语句ID一致。
mapper接口定义:
public interface UserDAO {
...
User findByName(User user);
...
}
Java方法调用:
...
UserDAO userDAO = session.getMapper(UserDAO.class);
User user = userDAO.findByName(new User(null, "test1", null));
...
映射器配置
MyBatis sql映射器配置方式有两种,一种是写sql映射xml文件,并在MyBatis配置文件中添加mapper;另一种是在mapper接口中通过注解的方式配置sql映射语句。
通过xml配置sql映射示例如下:
mapper接口定义:
public interface UserDAO {
...
User findByName(User user);
...
}
sql映射文件:
<mapper namespace="com.xiaofan.test.UserDAO">
...
<select id="findByName" resultMap="UserResultMap">
select * from user
<trim prefix="where" prefixOverrides="and | or" >
<if test="name != null">
and name = #{name}
</if>
<if test="age != null">
and age = #{age}
</if>
</trim>
</select>
...
</mapper>
MyBatis配置文件:
<configuration>
...
<mappers>
<mapper resource="user_mapper.xml"/>
</mappers>
...
</configuration>
第二种方式,通过注解方式定义mapper接口示例如下, 更多注解说明见文档:
Mapper接口类:
public interface UserDAO {
...
@Select("select * from user where name = #{name}")
User findByName(User user);
...
}
MyBatis配置文件:
<configuration>
...
<mappers>
<mapper class="com.xiaofan.test.UserDAO" />
</mappers>
...
</configuration>