Mybatis

不与夏虫语冰,不与井蛙语海,不与凡夫语道

1 简介

1.1 定义

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

1.1.1 背景

MyBatis本是apache的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis。2013年11月迁移到Github

1.1.2 为什么使用

  • 框架,自动化
  • 方便存入数据库
  • 技术没有高低之分
  • 用的人多,有生态
  • 优点

简单易学、灵活、解除sql与程序代码的耦合、提供映射标签、提供对象关系映射标签、提供xml标签

1.1.3 获取方式

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>

1.2 持久化

数据持久化就是将内存中的数据模型转换为存储模型,以及将存储模型转换为内存中的数据模型的统称. 数据模型可以是任何数据结构或对象模型,存储模型可以是关系模型、XML、二进制流等。cmp和Hibernate只是对象模型到关系模型之间转换的不同实现。

1.2.1 优点

  • 程序代码重用性强
  • 业务逻辑代码可读性强
  • 持久化技术可以自动优化,数据持久化对象的基本操作有:保存、更新、删除、查询等。

1.3 持久层

数据持久层位于领域层和基础架构层之间。由于对象范例和关系范例这两大领域之间存在“阻抗不匹配”,所以把数据持久层单独作为J2EE体系的一个层提出来的原因就是能够在对象-关系数据库之间提供一个成功的企业级映射解决方案,尽最大可能弥补这两种范例之间的差异。

  • 数据持久化的代码块
  • 通用的持久化

2 第一个Mybatis

搭建环境-导入包-编写代码-测试

2.1 创建一个数据库的表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
create database xhr;
use xhr;
create table if not exists t_admin(
user_id int(6) not null auto_increment COMMENT '用户ID',
dept_id int(3) comment '部门ID',
login_name varchar(50) DEFAULT '' comment '登录账号',
user_name varchar(50) DEFAULT '' comment '用户名',
email varchar(50) DEFAULT '' comment '用户邮箱',
phone varchar(30) DEFAULT '' comment '用户手机号码',
password varchar(50) DEFAULT '1' comment '用户密码',
user_type varchar(2) DEFAULT 'N' comment '类型',
user_state int(1) DEFAULT 0 comment '帐号状态',
create_by varchar(50) DEFAULT '' comment '创建者',
create_time timestamp DEFAULT CURRENT_TIMESTAMP comment '创建时间',
update_by varchar(50) DEFAULT '' comment '更新者',
update_time timestamp default CURRENT_TIMESTAMP comment '更新时间',
primary key(user_id)
)ENGINE = INNODB AUTO_INCREMENT=000001 default charset=utf8mb4;

2.2 新建项目

2.2.1 步骤

  • 普通的Maven项目
  • 删除src
  • 导入maven依赖
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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 父工程-->
<groupId>cn.xxy</groupId>
<artifactId>mybatis</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<mybatis.version>3.2.6</mybatis.version>
<mysql.version>5.1.30</mysql.version>
<junit.version>4.12</junit.version>
</properties>
<!--导入依赖-->
<dependencies>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
</dependencies>
</project>
  • 创建一个模块
  • 编写mybatis配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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://127.0.0.1:3306/temp1?useUnicode=true&amp;characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
</configuration>
  • 创建entity
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
package cn.xxy.entity;

/**
* @author xxy
*/
public class User {
private String id;
private String name;
private int age;

public User() {
}

public User(String id,String name,int age) {
this.id = id;
this.name = name;
this.age = age;
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

  • 创建mapper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package cn.xxy.mapper;

import cn.xxy.entity.User;

import java.util.List;

/**
* @author xxy
*/
public interface UserMapper {
/**
* 查询搜所有用户
* @return
*/
List<User> selectAllUser();
}

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="cn.xxy.mapper.UserMapper">

<select id="selectAllUser" resultType="User">
select * from user
</select>
</mapper>
  • 测试
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
package cn.xxy.mappertest;

import cn.xxy.entity.User;
import cn.xxy.mapper.UserMapper;
import cn.xxy.utils.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class usertest {
@Test
public void testUser() {
//获取sqlsession
SqlSession sqlSession = MybatisUtil.getSqlSession();
//方式一:getMapper
/* UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.selectAllUser();
for (User user : userList) {
System.out.println(user.toString());
}*/
//方式二:
List<User> userList = sqlSession.selectList("cn.xxy.mapper.UserMapper.selectAllUser");
for (User user : userList) {
System.out.println(user.toString());
}
//关闭sqlsession
sqlSession.close();
}
}

  • 可能出现的问题

  • 资源访问不到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<build>
<!-- 如果不添加此节点mybatis的mapper.xml文件都会被漏掉。 -->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
  • mapper配置文件初始化失败
1
2
3
4
看返回类型    
<select id="selectAllUser" resultType="cn.xxy.entity.User">
select * from user
</select>

3 CRUD

3.1 namespace【命名空间】

命名空间要与mapper包名的接口名字一样

3.2 增删改

需要提交事务

  • mapper
1
int insertUser(@Param("id") String id, @Param("name") String name, @Param("age") int age);
  • mapper.xml
1
2
3
<insert id="insertUser" parameterType="cn.xxy.entity.User">
insert into user(id,name,age) values(#{id},#{name},#{age})
</insert>
  • 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void testUserInsert() {
//获取sqlsession
SqlSession sqlSession = MybatisUtil.getSqlSession();
int result = 0;

try {
//方式一:getMapper
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
result = userMapper.insertUser("10","梨花",12);
if (result > 0) {
System.out.println("插入成功");
}
//提交事务
sqlSession.commit();
} finally {
//关闭sqlsession
sqlSession.close();
}

}
  • 可能出现问题
  • 数据库列的编码与类编码不同

修改数据库列的编码utfmb4,不要使用utf8这个是没用的

  • 提交事务
1
2
3
4
5
6
7
8
9
10
//提交事务
sqlSession.commit();
//自动提交事务
/**
* 从 SqlSessionFactory 中获取 SqlSession
* @return SqlSession
*/
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession(true);
}

3.3 传递参数

  • 只有一个基本类型,可以直接在sql取到
  • 对象传递参数
  • map传递【实际开发除了特殊情况,其他时候不推荐】
1
parameterType="map"

3.4 模糊查询

  • mapper
1
List<User> selectUserByName(String name);
  • xml
1
2
3
  <select id="selectUserByName" parameterType="String" resultType="cn.xxy.entity.User">
select * from user where name like concat('%',#{name,jdbcType=VARCHAR},'%')
</select>
  • 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void testSelectUserByName() {
//获取sqlsession
SqlSession sqlSession = MybatisUtil.getSqlSession();

try {
//方式一:getMapper
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.selectUserByName("J");
for (User user : userList) {
System.out.println(user.toString());
}
} finally {
//关闭sqlsession
sqlSession.close();
}

}

3.5 属性名和字段名不一致问题

  • 方式一:数据库操作语句起别名 as
1
2
3
<select id="selectUserByName" parameterType="String" resultType="cn.xxy.entity.User">
select id,name,age as age from user where name like concat('%',#{name,jdbcType=VARCHAR},'%')
</select>
  • 方式二:resultType用的是简称类名,需要结果集映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
   <resultMap id="BaseResultMap" type="cn.xxy.entity.User" >
<id column="id" property="id" jdbcType="VARCHAR" />
<result column="name" property="name" jdbcType="VARCHAR" />
<result column="age" property="age" jdbcType="VARCHAR" />
</resultMap>
<select id="selectAllLeave" resultMap="BaseResultMap" >
select
leave_id, tl.user_id as user_id, tl.dept_id as dept_id, leave_start, leave_end, leave_reasons, leave_phone, leave_state, tu.user_name as user_name, td.dept_name as dept_name, leave_reviewer, tl.create_by as create_by , tl.create_time as create_time
from t_leave tl
inner join t_user tu
on tl.user_id = tu.user_id
left join t_dept td
on tl.dept_id = td.dept_id
order by create_time desc
</select>

4. 配置

4.1 配置文件顶层结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
configuration(配置)

properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)

4.2 环境变量【environments】

MyBatis 可以配置成适应多种环境,不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

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
<?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"></properties>
<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="test">
<transactionManager type="MANAGED "/>
<!--连接数据库 dbcp c3p0 druid-->
<dataSource type="POOLED"> <!--type="[UNPOOLED|POOLED|JNDI]"-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/temp1?useUnicode=true&amp;characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="cn/xxy/mapper/UserMapper.xml" />
</mappers>

</configuration>

4.3 属性【properties】

引用外部文件,其中可以增加属性配置。相同优先使用外部配置文件

4.4 别名【typeAliases】

  • 自定义
1
2
3
4
5
6
7
8
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<typeAlias alias="Comment" type="domain.blog.Comment"/>
<typeAlias alias="Post" type="domain.blog.Post"/>
<typeAlias alias="Section" type="domain.blog.Section"/>
<typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>
  • 扫描包,如何要扫描又要自定义可以使用注解
1
2
3
<typeAliases>
<package name="domain.blog"/>
</typeAliases>
1
2
3
4
@Alias("author")
public class Author {
...
}

4.5 设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<settings>
全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。
<setting name="cacheEnabled" value="true"/>
延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。
<setting name="autoMappingBehavior" value="PARTIAL"/>
日志
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
驼峰命名
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

4.6 映射器【mappers】

  • 方式一:使用相对于类路径的资源引用 【推荐】
1
2
3
<mappers>
<mapper resource="cn/xxy/mapper/UserMapper.xml" />
</mappers>
  • 方式二:使用完全限定资源定位符(URL)【不建议】
1
2
3
4
5
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
  • 方式三:使用映射器接口实现类的完全限定类名

接口和他的Mapper配置文件必须同名

接口和他的Mapper配置文件必须在同一个包下

1
2
3
4
5
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
  • 方式四:将包内的映射器接口实现全部注册为映射器【包扫描】

接口和他的Mapper配置文件必须同名

接口和他的Mapper配置文件必须在同一个包下

1
2
3
<mappers>
<package name="org.mybatis.builder"/>
</mappers>

4.7 其他配置

  • 类型处理器(typeHandlers)

  • objectFactory(对象工厂)

  • plugins(插件)

    • MyBatis Plus
    • MyBatis Generator Core

4.8 生命周期

生命周期和作用域错误使用会导致严重的并发问题

  • SqlSessionFactoryBuilder

这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了【局部变量】

  • SqlSessionFactory

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在【相当于数据库连接池,全局变量】,因此 SqlSessionFactory 的最佳作用域是应用作用域。最简单使用单例模式或者静态单例模式

  • SqlSession

每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。

4.9 结果映射

ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。

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
<!-- 非常复杂的语句 -->
<select id="selectBlogDetails" resultMap="detailedBlogResultMap">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
A.id as author_id,
A.username as author_username,
A.password as author_password,
A.email as author_email,
A.bio as author_bio,
A.favourite_section as author_favourite_section,
P.id as post_id,
P.blog_id as post_blog_id,
P.author_id as post_author_id,
P.created_on as post_created_on,
P.section as post_section,
P.subject as post_subject,
P.draft as draft,
P.body as post_body,
C.id as comment_id,
C.post_id as comment_post_id,
C.name as comment_name,
C.comment as comment_text,
T.id as tag_id,
T.name as tag_name
from Blog B
left outer join Author A on B.author_id = A.id
left outer join Post P on B.id = P.blog_id
left outer join Comment C on P.id = C.post_id
left outer join Post_Tag PT on PT.post_id = P.id
left outer join Tag T on PT.tag_id = T.id
where B.id = #{id}
</select>
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
<!-- 非常复杂的结果映射 -->
<resultMap id="detailedBlogResultMap" type="Blog">
<constructor>
<idArg column="blog_id" javaType="int"/>
</constructor>
<result property="title" column="blog_title"/>
<association property="author" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
<result property="favouriteSection" column="author_favourite_section"/>
</association>
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<association property="author" javaType="Author"/>
<collection property="comments" ofType="Comment">
<id property="id" column="comment_id"/>
</collection>
<collection property="tags" ofType="Tag" >
<id property="id" column="tag_id"/>
</collection>
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost"/>
</discriminator>
</collection>
</resultMap>
  • constructor

    - 用于在实例化类时,注入结果到构造方法中

    • idArg - ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
    • arg - 将被注入到构造方法的一个普通结果
  • id – 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能

  • result – 注入到字段或 JavaBean 属性的普通结果

  • association

    – 一个复杂类型的关联;许多结果将包装成这种类型

    • 嵌套结果映射 – 关联可以是 resultMap 元素,或是对其它结果映射的引用
  • collection

    – 一个复杂类型的集合

    • 嵌套结果映射 – 集合可以是 resultMap 元素,或是对其它结果映射的引用
  • discriminator

    – 使用结果值来决定使用哪个

    resultMap

    • case

      – 基于某些值的结果映射

      • 嵌套结果映射 – case 也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射

5. 日志

5.1 日志工厂

出错,日志是最好的助手

  • 以前:sout、debug

  • 日志工厂

    SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING

5.2 日志设置

1
2
3
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

5.3 log4j【有漏洞了解使用方式即可】

可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

  • pom.xml
1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
  • log4j.properties
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
log4j.rootLogger=INFO,Console,File  
#定义日志输出目的地为控制台
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.Target=System.out
#可以灵活地指定日志输出格式,下面一行是指定具体的格式
log4j.appender.Console.layout = org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=[%c] - %m%n

#文件大小到达指定尺寸的时候产生一个新的文件
log4j.appender.File = org.apache.log4j.RollingFileAppender
#指定输出目录
log4j.appender.File.File = logs/mybatis.log
#定义文件最大大小
log4j.appender.File.MaxFileSize = 10MB
# 输出所以日志,如果换成DEBUG表示输出DEBUG以上级别日志
log4j.appender.File.Threshold = ALL
log4j.appender.File.layout = org.apache.log4j.PatternLayout
log4j.appender.File.layout.ConversionPattern =[%p] [%d{yyyy-MM-dd HH\:mm\:ss}][%c]%m%n

6. 分页

减少数据库查询压力

  • 使用limit,默认为0,-1这个数字bug已经修复

  • 使用rowbounds,开发中不建议使用

  • 使用插件mybatis-helper

使用 MyBatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的 SQL,然后重写 SQL,根据 dialect 方言,添加对应的物理分页语句和物理分页参数。

  • 使用拦截器

7. 注解

底层主要是动态代理,本质是反射

7.1 面向接口开发【解耦】

面向对象,以对象为单位,考虑它的属性和方法

面向过程,考虑具体的流程为一个单位

面向接口和上面两种不是一个问题,更体现为系统整体的架构

7.2 测试

  • 接口
1
2
@Select("select * from user")
List<User> selectAllUser1();
  • 绑定接口【很重要】
1
2
3
4
5
    <mappers>
<!-- <mapper resource="cn/xxy/mapper/UserMapper.xml" />-->
<mapper class="cn.xxy.mapper.UserMapper" />
</mappers>

  • 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void testSelectAllUser1() {
//获取sqlsession
SqlSession sqlSession = MybatisUtil.getSqlSession();
try {
//方式一:getMapper
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.selectAllUser1();
for (User user : userList) {
System.out.println(user.toString());
}

} finally {
//关闭sqlsession
sqlSession.close();
}

}

7.3 @Param

  • 有多个参数需要加上,一个建议加上,@Param(“age”)SQL引用的就是注解括号里的名字
1
int insertUser(@Param("id") String id,  String name, @Param("age") int age);

7.4 Lombok

适合就行

  • Idea库安装插件
  • 导入依赖
1
2
3
4
5
6
7
8
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>

  • 加注解
1
@Data

8. 映射

8.1 测试

8.1.1 新建实体类与数据库一致

8.1.2 建立Mapper接口和映射文件.xml文件

8.1.3 在核心配置文件绑定注册Mapper接口或文件【方式很多】

1
2
3
4
5
        <mapper resource="cn/xxy/mapper/UserMapper.xml" />
<!-- 注解使用class-->
<!-- <mapper class="cn.xxy.mapper.UserMapper" />-->
<mapper class="cn.xxy.mapper.TeacherMapper" />
</mappers>

8.1.4 测试查询是否成功

8.2 多对一映射

8.2.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
<resultMap id="BaseResultMap" type="cn.xxy.entity.Student" >
<id column="student_id" property="studentId" jdbcType="VARCHAR" />
<result column="student_name" property="studentName" jdbcType="VARCHAR" />
<result column="student_password" property="studentPassword" jdbcType="VARCHAR" />
<result column="student_gender" property="studentGender" jdbcType="VARCHAR" />
<result column="student_brithday" property="studentBrithday" jdbcType="VARCHAR" />
<result column="student_address" property="studentAddress" jdbcType="VARCHAR" />
<result column="teacher_id" property="teacherId" jdbcType="VARCHAR" />
</resultMap>

<resultMap id="StudentAndTeacher" type="cn.xxy.entity.Student" >
<id column="student_id" property="studentId" jdbcType="VARCHAR" />
<result column="student_name" property="studentName" jdbcType="VARCHAR" />
<result column="student_password" property="studentPassword" jdbcType="VARCHAR" />
<result column="student_gender" property="studentGender" jdbcType="VARCHAR" />
<result column="student_brithday" property="studentBrithday" jdbcType="VARCHAR" />
<result column="student_address" property="studentAddress" jdbcType="VARCHAR" />
<!-- 复杂的对象单独处理,对象association,集合collection-->
<association property="teacher" column="teacher_id" javaType="Teacher" select="selectTeacherByTeacherId">
</association>
</resultMap>

<select id="selectStudentAndTeacher" resultMap="StudentAndTeacher">
select * from tb_student
</select>

<select id="selectTeacherByTeacherId" parameterType="String" resultType="cn.xxy.entity.Teacher">
select * from t_teacher tt where tt.teacher_id = #{teacher_id}
</select>

8.2.2 按照查询嵌套查询【核心代码 联表查询】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<select id="selectStudentAndTeacher2" resultMap="StudentAndTeacher2">
select ts.student_id as student_id, ts.student_name as student_name,ts.student_password as student_password,ts.student_gender as student_gender ,ts.student_brithday as student_brithday ,ts.student_address as student_address,tt.teacher_name as teacher_name,tt.teacher_id as teacher_id
from tb_student ts
inner join t_teacher tt
where ts.teacher_id = tt.teacher_id
</select>


<resultMap id="StudentAndTeacher2" type="cn.xxy.entity.Student" >
<id column="student_id" property="studentId" jdbcType="VARCHAR" />
<result column="student_name" property="studentName" jdbcType="VARCHAR" />
<result column="student_password" property="studentPassword" jdbcType="VARCHAR" />
<result column="student_gender" property="studentGender" jdbcType="VARCHAR" />
<result column="student_brithday" property="studentBrithday" jdbcType="VARCHAR" />
<result column="student_address" property="studentAddress" jdbcType="VARCHAR" />
<!-- 复杂的对象单独处理,对象association,集合collection-->
<association property="teacher" javaType="cn.xxy.entity.Teacher" >
<result column="teacher_id" property="teacherId"></result>
<result property="teacherName" column="teacher_name"></result>
</association>
</resultMap>

8.3 一对多映射

8.3.1 按照结果嵌套处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--按照结果嵌套查询-->
<select id="selectStudentByTeacherId" parameterType="String" resultMap="StudentByTeacherId">
select ts.student_id as student_id, ts.student_name as student_name,ts.student_password as student_password,ts.student_gender as student_gender ,ts.student_brithday as student_brithday ,ts.student_address as student_address,tt.teacher_name as teacher_name,tt.teacher_id as teacher_id
from tb_student ts
inner join t_teacher tt
where ts.teacher_id = tt.teacher_id and tt.teacher_id = #{teacherId}
</select>

<resultMap id="StudentByTeacherId" type="cn.xxy.entity.Teacher1" >
<id column="teacher_id" property="teacherId" jdbcType="VARCHAR" />
<result column="teacher_name" property="teacherName" jdbcType="VARCHAR" />
<collection property="studentList" ofType="cn.xxy.entity.Student1">
<result column="student_id" property="studentId" jdbcType="VARCHAR" />
<result column="student_name" property="studentName" jdbcType="VARCHAR" />
<result column="student_password" property="studentPassword" jdbcType="VARCHAR" />
<result column="student_gender" property="studentGender" jdbcType="VARCHAR" />
<result column="student_brithday" property="studentBrithday" jdbcType="VARCHAR" />
<result column="student_address" property="studentAddress" jdbcType="VARCHAR" />
</collection>
</resultMap>

8.3.2 按照查询嵌套处理

1
2
3
4
5
6
7
8
9
10
11
<select id="selectTeachers" resultMap="TeacherStudent2">
select * from t_teacher tt where tt.teacher_id = #{teacherId}
</select>

<resultMap id="TeacherStudent2" type="cn.xxy.entity.Teacher1">
<collection property="studentList" javaType="ArrayList" ofType="cn.xxy.entity.Student1" select="selectStudentsByTeacherId2" column="teacher_id"></collection>
</resultMap>

<select id="selectStudentsByTeacherId2" resultType="cn.xxy.entity.Student1">
select * from tb_student ts where ts.teacher_id = #{teacherId}
</select>

9. 动态SQL

动态SQL就是能够根据不同的条件生成不同的SQL语句,本质还是SQL语句

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

9.1 if【会把if全部执行】

1
2
3
4
5
6
7
8
9
10
11
<select id="selectStudentByIf" parameterType="cn.xxy.entity.Student" resultMap="BaseResultMap">
select * from tb_student ts
<where>
<if test="teacherId != null">
teacher_id like #{teacherId}
</if>
<if test="studentName != null">
and student_name like #{studentName}
</if>
</where>
</select>
  • 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void testSelectStudentByIf() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
Student student = new Student();
student.setTeacherId("1");
student.setStudentName("tt");
try {
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> students = mapper.selectStudentByIf(student);
for (Student studentList : students) {
System.out.println(studentList);
}
} finally {
sqlSession.close();
}
}

9.2 choose (when, otherwise)【会到第一条真的语句,后面不再执行】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<select id="selectStudentByChoose" parameterType="cn.xxy.entity.Student" resultMap="BaseResultMap">
select * from tb_student ts
<where>
<choose>
<when test="teacherId != null">
teacher_id like #{teacherId}
</when>
<when test="studentName != null">
and student_name like #{studentName}
</when>
<otherwise>
and teacher_id = '1'
</otherwise>
</choose>
</where>
</select>

9.3 trim (where, set)【去前缀例如逗号,and,or这些】

使用 trim标签可以完成where标签相同的功能,

1
2
3
4
5
6
7
8
9
10
11
12
<trim prefix="WHERE" prefixOverrides="AND">
<if test="teacherId != null">
teacher_id like #{teacherId}
</if>
<if test="studentName != null">
and student_name like #{studentName}
</if>
</trim>

<trim prefix="SET" suffixOverrides=",">
...
</trim>

9.4 foreach

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>

9.5 SQL片段【相同的SQL语句】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<sql id="selectif">
<if test="teacherId != null">
teacher_id like #{teacherId}
</if>
<if test="studentName != null">
and student_name like #{studentName}
</if>
</sql>

<select id="selectStudentByIf" parameterType="cn.xxy.entity.Student" resultMap="BaseResultMap">
select * from tb_student ts
<where>
<include refid="selectif"/>
</where>
</select>
  • 注意:
  • 最好基于单表
  • SQL片段不要存在where标签

10. 缓存

解决高并发系统的性能

10.1 一级缓存

也叫本地缓存:SqlSession,默认开启,只在一次SqlSession中有效

10.2 缓存失效的情况

  • 查询不同的数据
  • 增删改会改变原来的数据,所以必定会刷新缓存
  • 查询不同的Mapper.xml
  • 手动清理缓存

10.3 二级缓存

也叫全局缓存,一级缓存作用域太低,一个名称空间【namespace】对应一个二级缓存,二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。

  • 开启全局缓存
1
2
  全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。
<setting name="cacheEnabled" value="true"/>
  • 在想要二级缓存的Mapper.xml中开启
1
<cache/>

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

​ 可用的清除策略有:

  • LRU – 最近最少使用:移除最长时间不被使用的对象。

  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。

  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。

  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

默认的清除策略是 LRU。

  • 小结
  1. 实体类需要序列化,否则会抛出没有序列化异常
  2. 只要开启二级缓存,在同一个Mapper下就有效
  3. 所有的数据都先放在一级缓存中
  4. 只有当前会话提交或者关闭,才会提交到二级缓存中
  5. 用户进来系统先查二级缓存,然后一级缓存,最后数据库

10.4 自定义缓存

Ehcache是一种广泛使用的开源Java分布式缓存。一般是使用Redis

11. 总结

对于Mybatis进行了系统的学习,接下来会对源码进行学习