1.MyBatis入门

1.1 MyBatis概述

官网网站:http://www.mybatis.org/mybatis-3/index.html

中文网站:http://www.mybatis.org/mybatis-3/zh/index.html

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录

1.2 MyBatis环境搭建

添加依赖

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
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>

src/main/resources目录下创建log4j.properties,并添加如下内容

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
# 日志开关

log4j.rootLogger=debug, Console

#Console

log4j.appender.Console=org.apache.log4j.ConsoleAppender

log4j.appender.Console.layout=org.apache.log4j.PatternLayout

log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n

#Project default level

log4j.logger.com.as.resource = INFO

log4j.logger.org.springframework.web = INFO



#DEBUG

log4j.logger.java.sql.Connection = DEBUG

log4j.logger.java.sql.Statement = DEBUG

log4j.logger.java.sql.PreparedStatement = DEBUG

log4j.logger.java.sql.ResultSet =DEBUG

#mybatis

log4j.logger.com.ibatis=DEBUG

log4j.logger.com.ibatis.common.jdbc.SimpleDataSource=DEBUG

log4j.logger.com.ibatis.common.jdbc.ScriptRunner=DEBUG

log4j.logger.com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate=DEBUG

表设计

实体类创建

1
2
3
4
5
6
7
8
9
10
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

private int id;
private String name;
private int age;
private Date createDate;
}

Dao层接口

1
2
3
4
5
6
7
8
9
10
11
12
public interface IUserDao {

List<User> findAllUser();

User getUserById(String id);

boolean saveUser(User user);

boolean updateUserById(User user);

boolean deleteUserById(String id);
}

接口映射文件xml

1
2
3
4
5
6
7
8
9
10
<?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.max.dao.IUserDao">

<select id="findAllUser" resultType="com.max.domain.User">
select * from t_user
</select>
</mapper>

MyBatis配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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_db"/>
<property name="username" value="root"/>
<property name="password" value="111111"/>
</dataSource>
</environment>
</environments>

<mappers>
<mapper resource="com/max/mapper/IUserDao.xml"/>
</mappers>
</configuration>

编写测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
public class IUserDaoTest {

public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
List<User> allUser = userDao.findAllUser();
System.out.println(allUser);
sqlSession.close();
}
}

2.MyBatis简单CRUD

2.1 插入

xml配置

1
2
3
<insert id="save" parameterType="com.max.domain.User">
insert into t_user(name, age) VALUES (#{name}, #{age})
</insert>

测试代码

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
public class UserTest {

SqlSession sqlSession;
IUserDao userDao;

@Before
public void init() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSession = sqlSessionFactory.openSession();
userDao = sqlSession.getMapper(IUserDao.class);
}

@Test
public void testSaveUser() {
userDao.save(new User(0, "bob", 20, null));
sqlSession.commit();
}

@After
public void destory() {
sqlSession.close();
}
}

2.2 更新

xml配置

1
2
3
<update id="update" parameterType="com.max.domain.User">
update t_user set age=#{age} where id=#{id}
</update>

测试代码

1
2
3
4
5
@Test
public void testUpdate() {
userDao.update(new User(6, null, 28, null));
sqlSession.commit();
}

2.3 删除

xml配置

1
2
3
<delete id="deleteById" parameterType="int">
delete from t_user where id=#{id};
</delete>

测试代码

1
2
3
4
5
@Test
public void testDelete() {
userDao.deleteById(6);
sqlSession.commit();
}

2.4 查询

  • 查询所有

    xml配置

    1
    2
    3
    <select id="findAll" resultType="com.max.domain.User">
    select * from t_user
    </select>

    测试代码

    1
    2
    3
    4
    @Test
    public void findAll() {
    userDao.findAll();
    }
  • 条件查询

    xml配置

    1
    2
    3
    <select id="findById" parameterType="int" resultType="com.max.domain.User">
    select * from t_user where id=#{id}
    </select>

    测试代码

    1
    2
    3
    4
    @Test
    public void testFindById() {
    userDao.findById(1);
    }
  • 模糊查询(#号)

    xml配置

    1
    2
    3
    <select id="findByName" parameterType="string" resultType="com.max.domain.User">
    select * from t_user where name like #{name}
    </select>

    测试代码

    1
    2
    3
    4
    5
    @Test
    public void testFindByName() {
    List<User> list = userDao.findByName("%m%");
    System.out.println(list);
    }

    输出结果

    1
    2
    Preparing: select * from t_user where name like ?
    Parameters: %m%(String)
  • 模糊查询($号)

    xml配置

    1
    2
    3
    <select id="findByName" parameterType="string" resultType="com.max.domain.User">
    select * from t_user where name like '%${_parameter}%'
    </select>

    测试代码

    1
    2
    3
    4
    5
    @Test
    public void testFindByName() {
    List<User> list = userDao.findByName("m");
    System.out.println(list);
    }

    输出结果

    1
    2
    Preparing: select * from t_user where name like '%m%'
    Parameters:

3.MyBatis常用配置

3.1 properties

properties标签主要用于将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
<?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>
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_db"/>
<property name="username" value="root"/>
<property name="password" value="111111"/>
</properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource 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="com/max/mapper/IUserDao.xml"/>
</mappers>
</configuration>

外部动态替换

jdbc.properties

1
2
3
4
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis_db
user=root
password=111111
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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="jdbc.properties" />
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${user}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>

<mappers>
<mapper resource="com/max/mapper/IUserDao.xml"/>
</mappers>
</configuration>

3.2 typeAliases

typeAliases,类别别名,是为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
<?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="jdbc.properties" />
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${user}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>

<typeAliases>
<typeAlias type="com.max.domain.User" alias="user" />
</typeAliases>

<mappers>
<mapper resource="com/max/mapper/IUserDao.xml"/>
</mappers>
</configuration>

3.3 重用语句块

这个元素可以被用来定义可重用的 SQL 代码段,这些 SQL 代码可以被包含在其他语句中。它可以(在加载的时候)被静态地设置参数。 在不同的包含语句中可以设置不同的值到参数占位符上。

1
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>

这个 SQL 片段可以被包含在其他语句中,例如:

1
2
3
4
5
6
7
<select id="selectUsers" resultType="map">
select
<include refid="userColumns"><property name="alias" value="t1"/></include>,
<include refid="userColumns"><property name="alias" value="t2"/></include>
from some_table t1
cross join some_table t2
</select>

4.MyBatis多表查询

多表查询通常的对应关系分为三种

  • 一对一
  • 一对多
  • 多对多

4.1 一对一

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
<?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.max.dao.IUserDao">

<resultMap id="userResultMap" type="user">
<id property="id" column="uid"/>
<result property="name" column="uname"/>
<result property="age" column="uage"/>
<result property="createDate" column="udate"/>
<association property="role" javaType="role">
<id property="id" column="rid"/>
<result property="roleName" column="rname" />
<result property="roleDesc" column="rdesc" />
</association>
</resultMap>

<select id="findAll" resultMap="userResultMap">
select u.id as uid,
u.name as uname,
u.age as uage,
u.create_date as udate,
r.id as rid,
r.role_name as rname,
r.role_desc as rdesc
from t_user u
inner join t_role r on u.role_id = r.id
</select>
</mapper>

4.2 一对多

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 mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.max.dao.IRoleDao">

<resultMap id="roleResultMap" type="role">
<id property="id" column="rid"/>
<result property="roleName" column="rname"/>
<result property="roleDesc" column="rdesc"/>
<collection property="users" ofType="user">
<id property="id" column="uid"/>
<result property="name" column="uname"/>
<result property="age" column="uage"/>
<result property="createDate" column="udate"/>
</collection>
</resultMap>

<select id="findAll" resultMap="roleResultMap">
select u.id as uid,
u.name as uname,
u.age as uage,
u.create_date as udate,
r.id as rid,
r.role_name as rname,
r.role_desc as rdesc
from t_user u
inner join t_role r on u.role_id = r.id
</select>

</mapper>

5.MyBatis动态SQL

MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦

常用动态SQL标签

  • if
  • choose
  • trim(where, set)
  • foreach

5.1 if标签

xml配置

1
2
3
4
5
6
<select id="findByAge" parameterType="user" resultType="user">
select * from t_user where 1=1
<if test="age != null and age > 0">
and age = #{age}
</if>
</select>

测试代码

1
2
3
4
5
6
7
@Test
public void testFindByAge() {
User user = new User();
user.setAge(18);
List<User> users = userDao.findByAge(user);
System.out.println(users);
}

5.2 choose标签

xml配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>

5.3 trim(where, set)标签

出现如下情况该如何解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>

MyBatis 有一个简单的处理,这在 90% 的情况下都会有用。而在不能使用的地方,你可以自定义处理方式来令其正常工作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>

where 元素只会在至少有一个子元素的条件返回 SQL 子句的情况下才去插入“WHERE”子句。而且,若语句的开头为“AND”或“OR”,where 元素也会将它们去除

tirm标签

属性 描述
prefix 给sql语句拼接的前缀
suffix 给sql语句拼接的后缀
prefixesToOverride 去除sql语句前面的关键字或者字符,该关键字或者字符由prefixesToOverride属性指定,假设该属性指定为”AND”,当sql语句的开头为”AND”,trim标签将会去除该”AND”
suffixesToOverride 去除sql语句后面的关键字或者字符,该关键字或者字符由suffixesToOverride属性指定

使用trim标签实现where标签

1
2
3
4
5
6
7
8
9
10
11
<trim prefix="WHERE" prefixOverrides="AND">
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</trim>

set 元素可以用于动态包含需要更新的列,而舍去其它的

1
2
3
4
5
6
7
8
9
10
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>

5.4 foreach标签

动态 SQL 的另外一个常用的操作需求是对一个集合进行遍历,通常是在构建 IN 条件语句的时候

1
2
3
4
5
6
7
8
9
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>

foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及在迭代结果之间放置分隔符。这个元素是很智能的,因此它不会偶然地附加多余的分隔符

5.5 bind标签

bind 元素可以从 OGNL 表达式中创建一个变量并将其绑定到上下文

1
2
3
4
5
<select id="selectBlogsLike" resultType="Blog">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
SELECT * FROM BLOG
WHERE title LIKE #{pattern}
</select>

5.6 多数据库支持

一个配置了“_databaseId”变量的 databaseIdProvider 可用于动态代码中,这样就可以根据不同的数据库厂商构建特定的语句

1
2
3
4
5
6
7
8
9
10
11
<insert id="insert">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
<if test="_databaseId == 'oracle'">
select seq_users.nextval from dual
</if>
<if test="_databaseId == 'db2'">
select nextval for seq_users from sysibm.sysdummy1"
</if>
</selectKey>
insert into users values (#{id}, #{name})
</insert>

6.MyBatis缓存

MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。

要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:

1
<cache/>

基本上就是这样。这个简单语句的效果如下:

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

这些属性可以通过 cache 元素的属性来修改。比如:

1
2
3
4
5
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>

这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

可用的清除策略有:

  • LRU – 最近最少使用:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

默认的清除策略是 LRU。

flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。

readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。