本文介绍MyBatis框架。
1. MyBatis概述
MyBatis是操作数据库的框架,相当于增强的JDBC,用该框架操作数据库,更加方便快捷。在Servlet章节中介绍过MVC框架,这是Web项目的开发架构。这里再介绍另一个概念:三层架构,该架构是项目开发常用的结构,不局限于Web软件。
三层架构包含了:界面层(User Interface Layer)、业务逻辑层(Business Logic Layer)和数据访问层(Data Access Layer)。这三层每层分别执行不同的工作,相当于将项目的功能进行分类。三层的职责如下所示:
- 界面层(表示层、视图层):主要功能是接受用户的数据,显示请求的处理结果。使用web页面和用户交互,手机App也属于表示层,用户在App中操作,业务逻辑在服务器端处理。(jsp、html、servlet等等,相当于controller包,后续用SpringMVC框架来做)
- 业务逻辑层:接收页面层传递过来的数据,检查数据并计算业务逻辑,调用数据访问层操作数据。(相当于service包,后续用Spring框架来做)
- 数据访问层(持久层):与数据库打交道。主要实现对数据的增删改查。将存储在数据库中的数据提交给业务层,同时将业务层的数据保存到数据库。(相当于dao包,后续用MyBatis框架来做。)
交互如下所示:
用户 ---> 界面层 ---> 业务逻辑层 ---> 数据访问层 ---> 数据库
本文介绍最简单的MyBatis框架,即主要用于数据访问。那么什么是框架呢?框架相当于模板,只是给了最基本的通用的功能。我们可以用该框架完成自己特定的、个性化的内容。
比如Servlet中的HttpServlet类,该类就是实现了各种请求类型的响应,但是响应的内容是404。我们可以重写doGet等方法来实现自定义的功能。再比如JDBC,是一个通用的访问数据库的API,我们只需要传入特定的参数即可。
既然MyBatis是针对JDBC的强化,那么为什么要对JDBC进行优化呢?JDBC存在哪些缺点?
- 代码比较多,开发效率低。
- 需要关注Connection、PreparedStatement、ResultSet等对象的创建和销毁。
- 对ResultSet查询的结果,需要自己封装成实体类。
- JDBC六步重复的代码比较多。
- 业务代码和数据库的操作混在一起。
虽然可以将JDBC的相关操作封装成工具类,但是仍然比较繁琐。因此针对上述缺点,出现了MyBatis框架。
MyBatis 本是 apache的一个开源项目 iBatis, 2010年这个项目由 apache software foundation 迁移到了 google code,并且改名为 MyBatis 。2013年 11月迁移到 Github。iBATIS 一词来源于“internet”和“abatis”的组合,是一个基于 Java 的持久层框架。iBATIS 提供的持久层(数据访问层)框架包括 SQL Maps和 Data Access Objects(DAOs) 。
SQL Maps:指的是可以把数据库表中的一行数据映射为一个Java对象,即不需要自己手动封装实体类了。
Data Access Objects:指的是数据访问,对数据库执行增删改查。
MyBatis提供了以下功能:
- 提供了创建Connection、PreparedStatement、ResultSet的能力,不用开发人员创建这些对象了。
- 提供了执行SQL语句的能力,不需要手动执行。
- 提供了遍历结果集,并把结果封装为Java对象的能力,将对象封装成List集合。
- 提供了关闭资源的能力,不用手动关闭Connection、PreparedStatement、ResultSet等资源。
- 我们所需要做的就是提供SQL语句。
总体来说,MyBatis是一个SQL映射框架,提供数据库的操作能力,相当于增强的JDBC,使得我们只需要集中精神写SQL即可。
2. MyBatis框架快速入门
为了方便后续的介绍,这里先简单介绍一个案例:查询数据库数据并输出。
2.1 下载MyBatis(可选)
其实可以不下载MyBatis的jar包,可以直接在maven中添加依赖。这里下载是为了看一下官方的参考文档。github连接:mybatis/mybatis-3: MyBatis SQL mapper framework for Java (github.com)
2.2 入门案例
实现步骤如下:
- 准备好数据表
- maven中加入MyBatis、MySQL驱动的依赖坐标
- 创建实体类,用于存储表中的一条记录(数据)
- 创建持久层的DAO接口,定义操作数据库的方法,一般一个表对应一个DAO接口。
- 创建一个mybatis使用的配置文件(.xml文件),叫做sql映射文件:写SQL语句,一般是一个表对应一个sql映射文件,和DAO接口在同一目录下。
- 创建mybatis的主配置文件,一个项目就一个主配置文件。主配置文件提供了数据库的连接信息和sql映射文件的位置信息。在resources目录下。
- 创建使用mybatis类,通过mybatis访问数据库。
可以类比一下JDBC六步和这里对应起来。
步骤如下所示:
数据表
maven加入mybatis、mysql驱动依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<!-- mybatis依赖 -->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!-- mysql驱动依赖 -->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>创建实体类,用于后续将数据库操作结果中的每一条记录封装成该对象
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
52package org.hianian.entity;
// 实体类,对应表中的一条数据;
public class User {
private int no;
private String login;
private String password;
private String name;
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return "User{" +
"no=" + no +
", login='" + login + '\'' +
", password='" + password + '\'' +
", name='" + name + '\'' +
'}';
}
}创建DAO接口,定义数据库操作方法
1
2
3
4
5
6
7
8
9
10
11
12import org.hianian.entity.User;
import java.util.List;
// 定义操作User数据表的接口
public interface UserDao {
// 查询数据表,返回User实体类组成的集合
// 注意,因为采用MyBatis,所以不再需要在该方法程序中书写SQL语句了,只需要将SQL映射文件中的SQL语句和该方法对应起来即可。
// 一般情况下,映射文件和接口处于同一目录下。文件的名称和接口保持一致。
public List<User> selectUsers();
}创建SQL映射文件,用于和DAO接口中的方法实现对应
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
<mapper namespace="org.hianian.dao.UserDao">
<select id="selectUsers" resultType="org.hianian.entity.User">
select no, login, password from t_user order by no
</select>
</mapper>
<!--
SQL映射文件:写SQL语句的,MyBatis会执行这些SQL语句;
1. 指定约束文件
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
mybatis-3-mapper.dtd 是约束文件的名称,扩展名是dtd的。
2. 约束文件的作用: 限制、检查在当前文件(即映射文件)中出现的标签、属性必须符合MyBatis的要求(要求在约束文件中)
3. mapper是当前文件的根标签,是必须的。
namespace是命名空间,唯一值,可以是自定义的字符串。但是一般情况下使用对应dao接口的权限定名称,即 包名.类名 。
比如本文件是对UserDao接口中的方法进行映射的,所以 namespace="org.hianian.dao.UserDao"。
4. 在当前文件中,可以使用特定的标签,表示数据库的特定操作。
<select>:表示执行查询,标签内的内容为select语句
<update>:表示更新数据库的操作,就是在<update>标签中写的是 update sql语句
<insert>:表示插入,执行的是insert语句
<delete>:表示删除,执行的是delete语句
5. select 表示查询操作
id属性:你要执行的SQL语法的唯一标识,MyBatis会使用这个id的值来找到要指定的sql语句;
和namespace一样可以自定义,但是一般情况下使用接口中的方法名称,也就是该标签中的语句实际上和接口中的指定方法对应,这样,使得配置文件和程序进行了对应。
resultType:表示结果类型(注意是全限定名称,包括包名),是SQL语句执行后得到ResultSet,集合中的元素类型即为resultType指定的类型,其实就是
实体类对象(相当于MyBatis自动封装了结果)。
-->创建MyBatis的主配置文件,对所有的SQL映射文件起作用
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
66
67
68
69
70
71
72
73
<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://ip:3306/mysql_test"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/hianian/dao/UserDao.xml"/>
</mappers>
</configuration>
<!--
MyBatis的主配置文件:主要定义了数据库的配置信息,SQL映射文件的位置。
1. 约束文件
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
mybatis-3-config.dtd 是约束文件的名称
2. configuration 是当前文件的根标签,根标签主要限定了两部分:
<environments>环境配置
<mappers>指定映射文件
3. environments 环境配置,也就是数据库的连接信息,属性default表示默认采用下面哪个environment数据库来连接,
值为environment的id值。
里面有多个 <environment> 子环境,即可以配置多个数据库,我们先配置一个。
4. environment 数据库信息配置
<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>
id属性是唯一值,用于表示该数据库环境的名称
transactionManager 表示mybatis的事务类型,type的取值为JDBC,表示使用JDBC中的Connection对象的commit、rollback做事务处理。
dataSource 表示数据源,用于连接数据库
type:表示数据源的类型,POLLED表示使用连接池。
4个property标签用于连接数据库,name属性值 driver、url、username、password是固定的,不能自定义。
5. mappers 指定SQL映射文件的位置,就是说当前这个主配置文件指定了数据库,
而之前的SQL映射文件(UserDao.xml)仅仅是指定了SQL语句,没有指明数据库。因此需要将二者关联起来,这就需要mappers标签。
但是,这里需要注意,指定的SQL映射文件的路径需要是程序编译之后的路径。即target/classes目录下。
这里需要注意,非resources文件夹下的非java文件要想出现在target中需要单独配置一下
一个 mapper 标签 指定一个文件的位置,resource属性值就是对应的文件位置,从类路径开始的路径信息。
这个类路径指定是 该类编译后的 target/classes/SQL映射文件xml路径
-->创建使用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
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
58package org.hianian;
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.hianian.entity.User;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.List;
public class AccessDB {
public static void main(String[] args) {
// 通过MyBatis访问数据库
// 1. 定义MyBatis主配置文件的名称,注意,从类路径的根开始(target/classes/)
String config = "mybatis.xml";
try {
// 2. 读取这个config表示的文件
InputStream inputStream = Resources.getResourceAsStream(config);
// 3. 创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// 4. 创建SqlSessionFactory对象
SqlSessionFactory factory = builder.build(inputStream);
// 5. 获取SqlSession对象(重要)
SqlSession sqlSession = factory.openSession();
// 6. 指定要执行的sql语句的标识(重要)
// 标识指的是 SQL映射文件中的 mapper标签的namespace属性 + "." + 标签的id值
// 可以看到,实际上通过namespace找到SQL映射文件(相当于找到数据库)
// 通过id值找到具体的语句
String sqlId = "org.hianian.dao.UserDao" + "." + "selectUsers";
// 7. 执行sql语句,即通过sqlId 找到语句
List<User> userList = sqlSession.selectList(sqlId);
// 8. 输出结果
Iterator<User> iterator = userList.iterator();
while(iterator.hasNext()){
User user = iterator.next();
System.out.println(user);
}
// 9. 关闭sqlSession对象
sqlSession.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}运行结果如下所示:
总体目录结构如下所示:
个人理解:
可以看到,在上述代码中,只有 封装的实体类 以及 数据库操作接口DAO,并没有具体数据操作语句,也就是说,该接口中的方法并没有实现。而是在SQL映射文件和MyBatis主配置文件中进行设置。
这在一定程度上实现了SQL数据操作和 Java程序分离。但是具体来说,这到底是怎么实现的呢?
首先需要maven,可以导入jar包依赖。之后就是MyBatis的 jar包的相关操作了,感觉类似Tomcat,对xml文件解析,解析指定标签,将其转换成Java语句,执行数据库操作?(待定、可以看一下MyBatis源码)
2.3 插入操作案例
注意,MyBatis默认没有提交事务,所以需要手动提交事务。
整体的框架不需要改变,只需要在DAO接口中新增插入方法,在SQL映射文件中新增<insert>标签插入语句即可。如下所示:
DAO接口新增方法:
1 | // 定义操作User数据表的接口 |
SQL映射文件新增插入语句:
1 | <!-- 插入语句 --> |
测试类中,新增内容如下所示:
1 | // 补充,插入操作 |
注意,经过上述操作,可以看到控制台输出的只是我们自己手动输出的内容,而SQL执行的语句以及参数都没有显示出来,可以开启日志,这样就可以显示日志信息了。
1 | <!-- 在主配置文件的configuration标签内,加入如下标签 --> |
可以看到插入操作中的具体日志如下所示:
即第一行表示采用标准输出日志;2~5行表示采用连接池;接下来的就是打开链接、创建连接对象,设置不自动提交,PreparedStatement对象与编译语句,传入参数,执行SQL语句,然后手动提交事务,关闭连接对象,将连接对象放回到连接池中。
2.4 主要类介绍
MyBatis主要用到一下四个类。
2.4.1 org.apache.ibatis.io.Resources
该类主要用于读取MyBatis主配置文件,返回输入流。
1 | InputStream inputStream = Resources.getResourceAsStream("mybatis.xml"); |
2.4.2 org.apache.ibatis.session.SqlSessionFactoryBuilder
该类主要用于创建SqlSessionFactory对象。
1 | SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); |
2.4.3 org.apache.ibatis.session.SqlSessionFactory(重点)
该类对象是一个重量级对象,即程序创建一个该对象耗时比较长,使用资源比较多。在整个项目中,该对象有一个就够用了。该类实际上是一个接口,反编译后的源码如下所示:
1 | public interface SqlSessionFactory { |
有两个实现类:org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
和org.apache.ibatis.session.SqlSessionManager
。
可以看到该接口主要是openSession方法,用于获取SqlSession对象。
2.4.4 org.apache.ibatis.session.SqlSession(重点)
该类也是一个接口,该接口主要是定义了实际操作数据库的各种方法,比如增删改查等操作,以及事务的提交回滚等等。有点类似PreparedStatement对象。
反编译后的源码如下所示:
1 | public interface SqlSession extends Closeable { |
该接口的有两个实现类:org.apache.ibatis.session.SqlSessionManager
和org.apache.ibatis.session.defaults.DefaultSqlSession
。
该接口的实现对象不是线程安全的,所以,在获取到该对象后,执行完sql语句后,需要执行SqlSession.close();
来保证它的使用是线程安全。
2.5 补充工具类
回顾JDBC,为了代码复用,将JDBC六步中的前几步封装成了工具类。那么在MyBatis这里,我们也可以将前面几个步骤封装成工具类,简化代码。
工具类如下所示:
1 | public class MyBatisUtils { |
为了简单起见,这里在测试类中进行测试:
1 | public class TestMyBatis { |
3. MyBatis框架Dao代理
3.1 Dao代理实现数据库操作
除去MyBatis将程序和映射文件关联的原理外,MyBatis的基本操作步骤已经明确了。但是我们前面都是将SQL相关操作封装成DAO类,而不是将其写在主程序中,因此上面的操作步骤可以封装成如下DAO接口的实现类。我们只需要调用方法获得结果即可。
DAO接口的实现类如下所示:
1 | public class UserDaoImpl implements UserDao { |
该实现类实际上就是在方法内部执行MyBatis的上述步骤,直接给调用者返回结果,如下所示:
1 | public class AccessDB { |
可以看到,这样做才是最常规的做法,但是这样做有没有缺点呢?
最基本的缺点就是,我们新增一个数据库,除了需要新增一个对应的实体类、以及接口 DAO外,还需要新增接口的实现类。
我们知道,新增一个数据库的话,在程序中新增实体类以及DAO接口这是必须的,那么这个DAO接口的实现类有必要写吗?实际上是没必要的,可以利用动态代理来创建该类。该类的本质就是调用映射文件中的特定的SQL语句。
如果通过动态代理创建实现类,那么怎么获取该类(要实现的接口)的名字呢?
答案是传参,将接口DAO的class传进去。
如何在实现类内部,将 namespace 和 id 传入呢?
这里其实很简单,将SQL映射文件中 namespace的值 设为 所映射接口的全限定名称即可(这也就是之前为什么建议这样做)。
那么id怎么传入呢?我们知道,SQL映射文件中的标签已经和方法对应起来了,而标签和id是一一对应的,所以当我们调用方法的时候,MyBatis就可以定位到接口的方法,然后就可以定位到SQL映射文件的标签,实际上就相当于定位到id了。所以一般情况下,Dao接口不建议写重载方法。
此时,就可以创建该代理类,并实现方法的调用了。
总结:
- namespace值要和Dao接口全限定名称一致。
- id值要和对应Dao接口中的方法名一致。
需要用到SqlSession的getMapper()方法
1 <T> T getMapper(Class<T> var1)
代码如下所示(此时不再需要实现Dao接口):
1 | public class AccessDB { |
注意,在2.3案例中,没有将id和Dao接口中的方法名一致,没有报错。但是这里采用动态代理,可以看到出现如下绑定错误,所以,抽象方法名和对应标签中的id值要一致,否则在动态代理这块就会出现绑定异常。
1 | Exception in thread "main" org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): org.hianian.dao.UserDao.insertStudent |
3.2 深入理解参数
这部分介绍如何将数据从Java程序中传入到SQL映射文件中。
3.2.1 parameterType
这个是写在SQL映射文件文件中的一个属性,表示Dao接口中的方法的参数的数据类型。这个值是Java中数据类型的全限定名称,如java.lang.String
等等,为了方便使用,MyBatis将全限定名称起了别名,可以直接采用别名,具体别名和Java数据类型的对应关系可以到参考文档中查看。
实际上这个参数就是为了告诉SQL映射文件,传入的参数的类型是什么。但是前面我们在反射中提到过,是可以获得方法的,那么此时就可以获得其参数的数据类型。所以这个参数可有可无,在前面的insert例子中就可以看到,并没有传入参数。简单案例如下所示:
1 | <select id="selectUsersByNo" parameterType="java.lang.Integer" resultType="org.hianian.entity.User"> |
3.2.2 传入一个简单类型参数
简单类型参数指的是基本数据类型(包括其对应的包装类型)。
我们知道,MyBatis会将接口中的方法和SQL映射文件一一对应起来,也就是将抽象方法和具体的内部实现一一对应起来。那么此时如何传入参数呢?
接口中的抽象方法的参数还是按照原来的方式写,即在方法参数括号中写参数;在SQL映射文件中,采用 #{任意字符}
的形式来接收数据。即表示SQL语句中的占位符。简单案例如下所示:
1 | // Dao接口中的抽象方法 |
1 | <select id="selectUsersByNo" parameterType="java.lang.Integer" resultType="org.hianian.entity.User"> |
可以看到,接收参数的时候,和JDBC中的PreparedStatement对象预编译那样,将SQL语句预先编译,保留占位符,然后再接收参数并拼接。
实际上和JDBC中的步骤一样:
- 创建Connection连接对象,PreparedStatement对象,预编译SQL语句,接收参数。
- 执行SQL语句封装为resultType指定的数据类型对象。进一步将这[些]对象封装成对应函数的返回结果。
3.2.3 传入多个参数——使用@Param(重点)
这种方法也被称为命名参数。上面的方法中是在SQL映射文件中进行设置,而Dao接口中不做改变。而本方法则是在Dao接口中的形参前面添加注解@Param("自定义参数")
,在SQL映射文件中依然采用#{自定义参数}
来接收参数,注意这两个自定义参数要一致。
我们知道,上面的方法只传了一个参数,所以在SQL映射文件中可以自定义变量,因为只有一个,无论自定义的是什么,肯定就是为了接受方法中唯一的那个参数。
而这里需要传入多个参数,这时候该怎么对应起来呢?本方法就是在Dao接口中的方法的参数添加注解,相当于告诉MyBatis程序中的位置,然后再SQL映射文件中,自定义的参数名称为该注解中的内容即可。简单案例如下所示:
1 | // Dao接口中的抽象方法 |
1 | <!-- 映射文件中的标签 --> |
结果如下所示:
3.2.4 传入多个参数——使用对象(重点)
有时候参数太多,在接口中形参太多就不太方便了,这时候可以将这些参数封装成一个对象,直接传入对象。
这种方法其实在前面2.3插入案例中已经用到了,方法的参数是User对象,而在SQL映射文件中则是用#{对象属性名, javaType=类型名称, jdbcType=数据类型}
来接收值。
注意,这是完整的用法,其中javaType用来指明这个参数在Java程序中的数据类型,jdbcType用来指明这个参数在数据库中的数据类型。这两种数据类型的具体名称都在MyBatis中指明了,可以查阅文档。
但是其实这两种数据类型实际上通过反射机制是可以获取到的,此时我们不需要在手动指明,所以这两个可以省略。简化方法为#{对象属性名}
。
案例如下所示:
1 | public int insertUser(User user); |
1 | <insert id="insertUser"> |
注意,在SQL映射文件中可以选择性的接收参数,可以不用全部都写。
3.2.5 传入多个参数——按位置(了解)
这种方式其实比较直观的,就是按照Dao接口中形参的位置来接收数据。类似JDBC中PreparedStatement中的占位符。
在mybatis-3.3版本及之前,参数的位置使用#{0},#{1}
等等这种表示,从3.4开始,参数的位置使用#{arg0}
或者#{param0}
等这种来表示。简单案例如下所示:
1 | public User selectUsersByIndex(int no, String name); |
1 | <select id="selectUsersByIndex" resultType="org.hianian.entity.User"> |
3.2.6 传入多个参数——使用Map(了解)
还有一种方式采用Map<String, Object>传参,将Map作为抽象方法的形参传入,在SQL映射文件中,采用Map中的key作为参数来接收对应的value,即#{key}
。
简单案例如下所示:
1 | public User selectUsersByMap(Map<String, Object> map); |
1 | <select id="selectUsersByMap" resultType="org.hianian.entity.User"> |
1 | Map<String, Object> map = new HashMap<>(); |
不建议使用Map传参,因为可读性较差,不知道Map中到底有多少个参数,不知道参数的具体类型。
3.2.7 #和$的区别(重点)
我们知道,JDBC中有PreparedStatement和Statement两种,前面提到的#{}
这种占位符是采用PreparedStatement语句来执行的;那么Statement语句该怎么接收变量呢?可以采用${}
这种方式来接收。
注意,${}
是简单的字符串拼接,不是占位符,没有SQL预编译操作,可能出现SQL注入等问题。所以一般情况下建议采用#{}
这种方式。
3.3 封装MyBatis输出结果
这部分介绍SQL执行后的结果是如何通过MyBatis封装成对象的。这里MyBatis是通过标签属性来确定的。
标题中的输出结果指的是MyBatis执行了SQL语句得到的Java对象。涉及到的标签属性有两个:resultType和resultMap。
3.3.1 resultType
该属性指的是SQL语句执行完毕之后,数据转为的Java对象,尤其是查询语句Select标签,一定要有resultType属性。
User类如下所示:
1 | public class User { |
大致的处理步骤如下所示:
MyBatis执行SQL语句,然后MyBatis通过该标签获得class类,调用该类的无参构造方法,创建对象。(所以Entity实体类必须有无参构造方法)
MyBatis把执行结果ResultSet结果中的列值赋值给类对象中同名的属性。(所以Entity实体类必须要有set、get方法)(注意,是调用,会调用
set字段名
方法给对应字段的属性赋值。如果没有,则不赋值)如上图所示,User对象有两个属性:no和logins,但是四个字段只有no和User对象一样,所以只给User对象中no赋值,其他属性为null。
另外,如果起了别名,也是可以的,总体来说,就是将查询结果中的字段赋值给resultType类型对象的对应属性值上。
之后,通过反射获取该Dao接口中方法的返回值,如果该方法的返回值,如果就是该对象类型,则直接返回;如果是List等集合,那么就将上面转换成的多个对象进一步封装成集合,返回给程序。
前面提到过,parameterType可以采用MyBatis定义的别名来代替Java中的类型,这里resultType同样可以使用MyBatis中的别名。另外,MyBatis中都是Java自带类型的别名,此时我们可以在MyBatis主配置文件中为我们自定义的类型起别名,如将org.hianian.entity.User
起别名为entityUser
。在MyBatis主配置文件中设置如下:
1 | <typeAliases> |
除此之外,还有另一种方式:
1 | <typeAliases> |
上面设置的是返回值类型为引用数据类型:
org.hianian.entity.User
,是将查询结果的字段名赋值给对象中同名的属性;除此之外,还可以将返回值类型赋值给
java.lang.Integer
等类型,这种是适用于返回值为整数型的接口方法。还可以赋值给
java.util.HashMap
类型,即<字段=值, 字段=值[, …]>类似这种的。
3.3.2 resultMap(常用)
resultMap指的是结果映射,用来指定列名和Java对象的属性之间的对应关系。用法主要如下:
- 自定义列赋值给Java对象的哪个属性(不再是同名对应关系)
- 当你的列名和属性名不一样时,可以用resultMap来设置。
resultMap是和<select>等标签同级的标签。
1 | <!-- 定义resultMap,id是自定义名称,用来标识resultMap; |
测试案例如下所示:
1 | <resultMap id="UserMap" type="org.hianian.entity.User"> |
3.3.3 实体类属性名和列名不同的处理方式
通过上述讲解,实体类属性名和列名不一致的处理方式主要有以下几种:
- resultMap映射
- SQL语句起别名,别名和属性名一致
3.4 模糊查询like
这里讲解一下模糊查询的例子,主要有两种实现方法:
- 将like后面的内容作为参数,传入到SQL映射文件中,即
"%李%"
;SQL中接收select * from t_user where name like #{name}
。(推荐) - 将like后面的内容中的关键内容作为参数,其余的通配符不用,传入到SQL映射文件中,即
李
;SQL中接收并拼接:select * from t_user where name like "%" #{name} "%"
4. MyBatis框架动态SQL
动态SQL指的是通过MyBatis提供的各种标签对条件作出判断以实现动态拼接SQL语句。这里的条件判断使用的表达式为OGNL表达式。常用的动态SQL标签有<if>、<where>、<choose>、<foreach>等。MyBatis的动态SQL语句,与JSTL中的语句非常相似。
动态SQL,主要用于解决查询条件不确定的情况:在程序运行期间,根据用户提交的查询条件进行查询。提交的查询条件不同,执行的SQL语句不同。若将每种可能的情况均逐一列出,对所有条件进行排列组合,将会出现大量的SQL语句。此时,可使用动态SQL来解决这样的问题。
从另一个角度讲,Java程序如果设了SQL查询条件,那么就只能是一个条件对应一个Dao接口方法,这样做使得程序代码很繁重,并且对应的SQL映射文件中的SQL语句也比较多。所以最好是将所有可能的条件统一封装成一个参数,然后在SQL映射文件中进行条件判断以及SQL语句拼接。因此就出现了动态SQL。
没有被标签覆盖的内容被称为主SQL语句。
4.1 动态SQL之<if>
需求:查询表中的数据,根据条件来查询,但是这个条件可能有也可能没有,比如,可能有年龄条件,年龄必须大于18岁;可能有姓名,必须是李姓开头的名字。
这样,需要判断条件是否有,如果有的话,就将其拼接到SQL语句中,如果没有则不进行操作,这就需要用到<if>标签。
1 | <if test="使用参数Java对象的属性值作为判断条件,语法:属性 条件表达式 值"></if> |
动态SQL所对应的接口方法,用Java对象来传参,所以在SQL映射文件中用#{属性名}
来接收。
1 | <select id="selectUserIf" resultType="org.hianian.entity.User"> |
但是上面的语句明显有问题,如果第一个条件不满足,第二条件满足,那么就会在where后面直接出现了and关键字,就会出现了SQL语法错误。所以,应该使得后面的条件不应该总体的语法。如下面所示:
1 | <select id="selectUserIf" resultType="org.hianian.entity.User"> |
即在where条件后先添加一个永为真的条件,后续的条件前面直接带上and、or等连接词。不过这种只是解决if标签的一种方式,下面的<where>标签可以完全解决这个问题。
4.2 动态SQL之<where>
where标签用来包含多个<if>标签,当多个if有一个成立的,where标签会自动增加一个where关键字,并去掉if中多余的and、or等关键字。
1 | <!-- where标签语法 --> |
案例如下所示,不需要单独再考虑条件以及关键字and、or:
1 | <select id="selectUserIf" resultType="org.hianian.entity.User"> |
4.3 动态SQL之<foreach>
foreach标签用于实现对于数组与集合的遍历,主要用在SQL中的in语句中,比如select * from student where id in (1001, 1002, 1003)
这种SQL语句,参数1001,1002,1003怎么提供呢?SQL映射文件怎么获取呢?(注意,这是in条件可能存在可能没有)所以采用foreach标签。
对其使用,需要注意:
- collection表示要遍历的集合类型,如list、array等。
- open、close、separator为对遍历内容的SQL拼接。
语法如下所示:
1 | <foreach collection="集合类型" open="开始的字符" close="结束的字符" item="集合中的成员,自定义" separator="集合成员之间的分隔符"> |
案例如下所示:
1 | <select id="selectUsersByForeach" resultType="org.hianian.entity.User"> |
1 |
|
另一个案例如下:
1 | <select id="selectUsersByForeach2" resultType="org.hianian.entity.User"> |
1 |
|
foreach中的集合类型里面的元素,可以是基本数据类型,也可以是引用数据类型。对于基本数据类型,接收值的时候,直接
#{item值}
即可;对于引用数据类型,接收值的时候,直接#{item值.对象属性}
来获取值。另外,open和close可以省略,自己在SQL语句中拼接。
这里foreach相当于实现了可变参数传参。
4.4 动态SQL之代码片段
代码片段是复用代码的一种方式。<sql>标签用于定义SQL片段,以便其他SQL标签复用。而其他标签使用该SQL片段,需要使用<include>字标签。该<sql>标签可以定义SQL语句中的任何部分,所以<include>字标签可以放在动态SQL中的任何位置。
用法如下:
1 | <sql id="唯一表示"> |
调用语法如下(以select标签为例):
1 | <select> |
案例如下所示:
1 | <sql id="selectAll"> |
5. MyBatis配置文件
这部分内容讲解一下配置文件中的相关知识。
5.1 主配置文件
主配置文件中的大部分内容在上面已经陈述了,并且其余的内容基本上采用默认值即可。至于datasource、transactionManager等标签如下面小节所示。
5.2 dataSource标签
<dataSource type="POOLED">
标签表示数据源,即连接数据库的相关部分,实现了javax.sql.DataSource
接口,用于创建连接对象。
- type属性为POOLED指明了创建多个连接对象,形成连接池;
- type属性为UNPOOLED表示不使用连接池;
- type属性为JNDI,表示Java命名和目录服务(windows注册表),了解。
5.3 事务
<transactionManager type="JDBC"/>
标签表示提交事务回滚事务的方式。该标签type属性的值有两个:
- JDBC:表示MyBatis底层是调用JDBC中的Connection对象的commit、rollback等来进行事务的相关操作。
- MANAGED:表示把MyBatis的事务处理委托给其他的容器(比如一个服务器软件、一个框架(sping)等等)
5.4 使用数据库属性配置文件(重点)
从上面可以看到,数据库的相关配置信息是在主配置文件中。如果有多个数据库,可以直接写多个数据库配置信息。但是这样做不方便修改和保存。因此可以将数据库连接信息放到一个单独的文件中,和MyBatis主配置文件分开,这就是数据库的属性配置文件。目的是便于修改、保存,处理多个数据库的信息。步骤如下所示:
- 在resources目录中定义一个属性配置文件:xxx.proeprties,如jdbc.properties。在属性配置文件中定义数据,格式是:
key=value
。 - 在mybatis主配置文件中,使用<properties>标签指定文件的位置,在需要使用值的地方,使用
${key}
获取对应的value即可。
这时候如果想要修改数据库连接方面的内容,直接在专属的属性配置文件中修改即可,比较直观,不易犯错。
配置文件如下所示:
1 | jdbc.driver=com.mysql.jdbc.Driver |
主配置文件如下所示:
1 | <properties resource="jdbc.properties"></properties> |
5.5 typeAliases(类型别名)
别名如前面所示,这里不再赘述。
5.6 mappers(映射器,重点)
mapper标签用于指定映射文件,一个标签只能指定一个文件,如果有多个映射文件,此时就需要有多个mapper标签。但是这样做很不方便,因为大型项目中会有很多实体类,并且都有对应的Dao接口操作,这样逐个添加不好管理。
因此可使用另一种方式(重点):使用包名,即<package name=”mapper文件xml所在的包名”>。和3.3.1上面的别名一样,相当于把包名导入,即里面的所有xml文件都导入了进来。注意,该package标签仍然是在mappers标签里面使用。并且有以下几点要求:
- mapper文件名称需要和接口名称一样,且区分大小写。
- mapper文件和dao接口需要在同一目录下。
这也就是为什么在前面建议名称一致,且在同一目录下,就是为了方便这里统一映射。
6. 扩展
6.1 分页
我们知道MySQL中有分页查询操作,基于limit关键字可以实现。但是为了方便快捷,某作者出现了一个小工具PageHelper,可以帮我们在MyBatis中实现limit操作,并且支持许多主流的数据库。
这里不再赘述,网络上检索一些帮助文档即可:MyBatis 分页插件 PageHelper
6.2 逆向工程
前面我们讲的是,利用MyBatis来访问数据库,即需要编写实体类、Dao、以及映射文件等等。其实仔细想想,实体类对象对应的就是一条记录,Dao对应的就是具体的SQL操作,映射文件则是具体的SQL语句。其实这些都是固定的,完全可以利用程序来实现上面的三部分,这就是MyBatis逆向工程。案例如下所示:
新建项目(普通项目即可)
添加插件:(注意,这是插件,不是依赖。需要放在build标签的plugins里面,和什么clean、install等类似。)
1
2
3
4
5
6
7
8
9
10
11
12
13
14<build>
<plugins>
<!-- MyBatis逆向工程插件 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<configuration>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
</plugin>
</plugins>
</build>添加配置文件,告诉插件如下信息:
数据库连接信息
1
2
3
4
5D:/profession/maven/maven_repository/mysql/mysql-connector-java/8.0.28/mysql-connector-java-8.0.28.jar =
com.mysql.cj.jdbc.Driver =
jdbc:mysql://ip:port/crm2022?serverTimezone=UTC&characterEncoding=utf-8 =
crm2022root =
crm2022password =数据表的信息
生成后的代码保存的目录
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<generatorConfiguration>
<!--导入属性配置generator.properties-->
<properties resource="generator.properties"/>
<!--指定特定数据库的jdbc驱动jar包的位置-->
<classPathEntry location="${jdbc.driverLocation}"/>
<context id="default" targetRuntime="MyBatis3">
<!-- optional,旨在创建class时,对注释进行控制 -->
<commentGenerator>
<property name="suppressDate" value="true"/>
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!--jdbc的数据库连接 -->
<jdbcConnection
driverClass="${jdbc.driverClass}"
connectionURL="${jdbc.connectionURL}"
userId="${jdbc.userId}"
password="${jdbc.userPassword}">
<!--MySQL 不支持 schema 或者 catalog 所以需要添加这个-->
<!-- 不然会出现生成器把其他数据库的同名表生成下来的问题 -->
<!-- 现象就是某个类中出现了数据库表里面没有的字段 -->
<property name="nullCatalogMeansCurrent" value="true"/>
</jdbcConnection>
<!-- 非必需,类型处理器,在数据库类型和java类型之间的转换控制-->
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!-- Model模型生成器, 用来生成含有主键key的类,记录类 以及查询Example类【实体类】
targetPackage 指定生成的model生成所在项目路径下的包名【源码路径下的包名】
targetProject 指定在该项目下所在的路径【Java源码路径】
-->
<javaModelGenerator targetPackage="org.hianian.settings.entity"
targetProject="D:/others/study_source/Java/project/crm/src/main/java">
<!-- 是否允许子包,即targetPackage.schemaName.tableName -->
<property name="enableSubPackages" value="false"/>
<!-- 是否对model添加 构造函数 -->
<property name="constructorBased" value="true"/>
<!-- 是否对类CHAR类型的列的数据进行trim操作 -->
<property name="trimStrings" value="true"/>
<!-- 建立的Model对象是否 不可改变 即生成的Model对象不会有 setter方法,只有构造方法 -->
<property name="immutable" value="false"/>
</javaModelGenerator>
<!--Mapper映射文件生成所在的目录 为每一个数据库的表生成对应的SqlMap文件 【mapper或者dao】-->
<sqlMapGenerator targetPackage="org.hianian.settings.dao"
targetProject="D:/others/study_source/Java/project/crm/src/main/java">
<property name="enableSubPackages" value="false"/>
</sqlMapGenerator>
<!-- 客户端代码,生成易于使用的针对Model对象和XML配置文件 的代码 【mapper接口】
type="ANNOTATEDMAPPER",生成Java Model 和基于注解的Mapper对象
type="MIXEDMAPPER",生成基于注解的Java Model 和相应的Mapper对象
type="XMLMAPPER",生成SQLMap XML文件和独立的Mapper接口
-->
<javaClientGenerator targetPackage="org.hianian.settings.dao"
targetProject="D:/others/study_source/Java/project/crm/src/main/java" type="XMLMAPPER">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<!--要执行逆向工程所用到的表,【针对哪个表来生成上述三个文件,注意,每次只写一张表】-->
<table tableName="tbl_user" domainObjectName="User"
enableCountByExample="false" enableUpdateByExample="false"
enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false"/>
<!-- <table tableName="category" />-->
</context>
</generatorConfiguration>
生成后的文件如下所示:
7. 总结
MyBatis是JDBC的升级版,将数据库操作和代码部分分离。所以以前代码中的数据库操作部分有:
- JDBC驱动、连接对象等等;
- Dao接口的实现类
- SQL语句PreparedStatement对象执行
- SQL语句接收参数
- SQL执行结果封装成实体类或者其他对象
都会被映射为MyBatis操作:
- MyBatis主配置文件
- SqlSession.getMapper(),反射创建实现类,Dao代理
- MyBatis的SQL映射文件
- parameterType属性、
#{类属性名}
、@Param
接收参数 - resultType、resultMap将结果封装成对应的Java对象
另外,还有动态SQL以及配置文件的相关用法。
8. 备注
参考B站《动力节点》。