JavaWeb_13_MyBatis


本文介绍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存在哪些缺点?

  1. 代码比较多,开发效率低。
  2. 需要关注Connection、PreparedStatement、ResultSet等对象的创建和销毁。
  3. 对ResultSet查询的结果,需要自己封装成实体类。
  4. JDBC六步重复的代码比较多。
  5. 业务代码和数据库的操作混在一起。

虽然可以将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提供了以下功能:

  1. 提供了创建Connection、PreparedStatement、ResultSet的能力,不用开发人员创建这些对象了。
  2. 提供了执行SQL语句的能力,不需要手动执行。
  3. 提供了遍历结果集,并把结果封装为Java对象的能力,将对象封装成List集合。
  4. 提供了关闭资源的能力,不用手动关闭Connection、PreparedStatement、ResultSet等资源。
  5. 我们所需要做的就是提供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)

mybatis_001.png (573×150) (gitee.io)

2.2 入门案例

实现步骤如下:

  1. 准备好数据表
  2. maven中加入MyBatis、MySQL驱动的依赖坐标
  3. 创建实体类,用于存储表中的一条记录(数据)
  4. 创建持久层的DAO接口,定义操作数据库的方法,一般一个表对应一个DAO接口。
  5. 创建一个mybatis使用的配置文件(.xml文件),叫做sql映射文件:写SQL语句,一般是一个表对应一个sql映射文件,和DAO接口在同一目录下。
  6. 创建mybatis的主配置文件,一个项目就一个主配置文件。主配置文件提供了数据库的连接信息和sql映射文件的位置信息。在resources目录下。
  7. 创建使用mybatis类,通过mybatis访问数据库。

可以类比一下JDBC六步和这里对应起来。

步骤如下所示:

  1. 数据表

    mybatis_002.png (685×494) (gitee.io)

  2. 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>
  3. 创建实体类,用于后续将数据库操作结果中的每一条记录封装成该对象

    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
    package 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;
    }

    @Override
    public String toString() {
    return "User{" +
    "no=" + no +
    ", login='" + login + '\'' +
    ", password='" + password + '\'' +
    ", name='" + name + '\'' +
    '}';
    }
    }
  4. 创建DAO接口,定义数据库操作方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import org.hianian.entity.User;

    import java.util.List;

    // 定义操作User数据表的接口
    public interface UserDao {

    // 查询数据表,返回User实体类组成的集合
    // 注意,因为采用MyBatis,所以不再需要在该方法程序中书写SQL语句了,只需要将SQL映射文件中的SQL语句和该方法对应起来即可。
    // 一般情况下,映射文件和接口处于同一目录下。文件的名称和接口保持一致。
    public List<User> selectUsers();
    }
  5. 创建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
    <?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="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自动封装了结果)。
    -->
  6. 创建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
    <?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://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路径

    -->
  7. 创建使用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
    58
    package 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();
    }
    }
    }
  8. 运行结果如下所示:

    mybatis_003.png (772×354) (gitee.io)

  9. 总体目录结构如下所示:

    mybatis_004.png (559×712) (gitee.io)

个人理解:

可以看到,在上述代码中,只有 封装的实体类 以及 数据库操作接口DAO,并没有具体数据操作语句,也就是说,该接口中的方法并没有实现。而是在SQL映射文件和MyBatis主配置文件中进行设置。

这在一定程度上实现了SQL数据操作和 Java程序分离。但是具体来说,这到底是怎么实现的呢?

首先需要maven,可以导入jar包依赖。之后就是MyBatis的 jar包的相关操作了,感觉类似Tomcat,对xml文件解析,解析指定标签,将其转换成Java语句,执行数据库操作?(待定、可以看一下MyBatis源码

2.3 插入操作案例

注意,MyBatis默认没有提交事务,所以需要手动提交事务。

整体的框架不需要改变,只需要在DAO接口中新增插入方法,在SQL映射文件中新增<insert>标签插入语句即可。如下所示:

DAO接口新增方法:

1
2
3
4
5
6
7
8
9
10
11
12
// 定义操作User数据表的接口
public interface UserDao {

// 查询数据表,返回User实体类组成的集合
// 注意,因为采用MyBatis,所以不再需要在该方法程序中书写SQL语句了,只需要将SQL映射文件中的SQL语句和该方法对应起来即可。
// 一般情况下,映射文件和接口处于同一目录下。文件的名称和接口保持一致。
public List<User> selectUsers();

// 插入方法,注意JDBC中共SQL插入语句返回结果是整数,所以这里返回的也是整数int
// 参数即为要插入的数据(每一条数据封装成的类型)
public int insertStudent(User user);
}

SQL映射文件新增插入语句:

1
2
3
4
5
6
<!-- 插入语句 -->
<!-- 注意,一般情况下插入的数据是动态的,即需要通过User获取具体的数据,也就是下面的 #{} -->
<!-- #{}里面的内容代表传入参数对象的属性,即通过属性获取传入对象的具体属性值 -->
<insert id="insertUser">
insert into t_user values(#{no}, #{login}, #{password}, #{name})
</insert>

测试类中,新增内容如下所示:

1
2
3
4
5
6
7
8
// 补充,插入操作
// 注意,MyBatis默认是不提交事务的,所以这里在插入操作结束后,需要手动提交事务。
String sqlId = "org.hianian.dao.UserDao" + "." + "insertUser";
int result = sqlSession.insert(sqlId, new User(11, "hianian", "hianian123", "hianian"));

// 手动提交事务
sqlSession.commit();
System.out.println("插入结果:" + result);

注意,经过上述操作,可以看到控制台输出的只是我们自己手动输出的内容,而SQL执行的语句以及参数都没有显示出来,可以开启日志,这样就可以显示日志信息了。

1
2
3
4
<!-- 在主配置文件的configuration标签内,加入如下标签 -->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"></setting>
</settings>

可以看到插入操作中的具体日志如下所示:

mybatis_005.png (1090×597) (gitee.io)

即第一行表示采用标准输出日志;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
2
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream);

2.4.3 org.apache.ibatis.session.SqlSessionFactory(重点)

该类对象是一个重量级对象,即程序创建一个该对象耗时比较长,使用资源比较多。在整个项目中,该对象有一个就够用了。该类实际上是一个接口,反编译后的源码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public interface SqlSessionFactory {

// 获取非自动提交事务的SqlSession对象
SqlSession openSession();

// 参数为true的话,表明自动提交事务
SqlSession openSession(boolean var1);

SqlSession openSession(Connection var1);

SqlSession openSession(TransactionIsolationLevel var1);

SqlSession openSession(ExecutorType var1);

SqlSession openSession(ExecutorType var1, boolean var2);

SqlSession openSession(ExecutorType var1, TransactionIsolationLevel var2);

SqlSession openSession(ExecutorType var1, Connection var2);

Configuration getConfiguration();
}

有两个实现类:org.apache.ibatis.session.defaults.DefaultSqlSessionFactoryorg.apache.ibatis.session.SqlSessionManager

可以看到该接口主要是openSession方法,用于获取SqlSession对象。

2.4.4 org.apache.ibatis.session.SqlSession(重点)

该类也是一个接口,该接口主要是定义了实际操作数据库的各种方法,比如增删改查等操作,以及事务的提交回滚等等。有点类似PreparedStatement对象。

反编译后的源码如下所示:

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
public interface SqlSession extends Closeable {
<T> T selectOne(String var1);

<T> T selectOne(String var1, Object var2);

<E> List<E> selectList(String var1);

<E> List<E> selectList(String var1, Object var2);

<E> List<E> selectList(String var1, Object var2, RowBounds var3);

<K, V> Map<K, V> selectMap(String var1, String var2);

<K, V> Map<K, V> selectMap(String var1, Object var2, String var3);

<K, V> Map<K, V> selectMap(String var1, Object var2, String var3, RowBounds var4);

<T> Cursor<T> selectCursor(String var1);

<T> Cursor<T> selectCursor(String var1, Object var2);

<T> Cursor<T> selectCursor(String var1, Object var2, RowBounds var3);

void select(String var1, Object var2, ResultHandler var3);

void select(String var1, ResultHandler var2);

void select(String var1, Object var2, RowBounds var3, ResultHandler var4);

int insert(String var1);

int insert(String var1, Object var2);

int update(String var1);

int update(String var1, Object var2);

int delete(String var1);

int delete(String var1, Object var2);

void commit();

void commit(boolean var1);

void rollback();

void rollback(boolean var1);

List<BatchResult> flushStatements();

void close();

void clearCache();

Configuration getConfiguration();

<T> T getMapper(Class<T> var1);

Connection getConnection();
}

该接口的有两个实现类:org.apache.ibatis.session.SqlSessionManagerorg.apache.ibatis.session.defaults.DefaultSqlSession

该接口的实现对象不是线程安全的,所以,在获取到该对象后,执行完sql语句后,需要执行SqlSession.close();来保证它的使用是线程安全。

2.5 补充工具类

回顾JDBC,为了代码复用,将JDBC六步中的前几步封装成了工具类。那么在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
public class MyBatisUtils {

private static SqlSessionFactory sqlSessionFactory = null;

// sqlSessionFactory对象占用的资源较大,一般一个项目最好只有一个,所以这里采用静态代码块获取
static {
String config = "mybatis.xml";

try {
InputStream inputStream = Resources.getResourceAsStream(config);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
sqlSessionFactory = builder.build(inputStream);

} catch (IOException e) {
e.printStackTrace();
}

}

// 获取SqlSession
public static SqlSession getSqlSession() throws IOException {

if(sqlSessionFactory != null){
return sqlSessionFactory.openSession();
} else {

// 因为本来上面静态代码块中的异常是应该让调用者知道的,但是静态代码块不能抛出异常,
// 所以这里在这里抛出异常,这里抛出异常也没关系。
// 如果factory为空,则抛出异常
throw new IOException();
}
}
}

为了简单起见,这里在测试类中进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class TestMyBatis {

@Test
public void testInsert(){
try {

// 通过工具类获取SqlSession对象。直接一步到位
SqlSession sqlSession = MyBatisUtils.getSqlSession();

String sqlId = "org.hianian.dao.UserDao" + "." + "insertUser";

int result = sqlSession.insert(sqlId, new User(12, "asdf", "asdf", "zxcv"));

sqlSession.commit();
sqlSession.close();

System.out.println(result);

} catch (IOException e) {
e.printStackTrace();
}
}
}

3. MyBatis框架Dao代理

3.1 Dao代理实现数据库操作

除去MyBatis将程序和映射文件关联的原理外,MyBatis的基本操作步骤已经明确了。但是我们前面都是将SQL相关操作封装成DAO类,而不是将其写在主程序中,因此上面的操作步骤可以封装成如下DAO接口的实现类。我们只需要调用方法获得结果即可。

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
42
43
public class UserDaoImpl implements UserDao {
@Override
public List<User> selectUsers() {

List<User> userList = null;
try {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
String sqlId = "org.hianian.dao.UserDao" + "." + "selectUsers";

userList = sqlSession.selectList(sqlId);

sqlSession.close();

} catch (IOException e) {
e.printStackTrace();
}

return userList;

}

@Override
public int insertUser(User user) {

int result = 0;

try {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
String sqlId = "org.hianian.dao.UserDao" + "." + "insertUser";

result = sqlSession.insert(sqlId, user);

sqlSession.commit();

sqlSession.close();

} catch (IOException e) {
e.printStackTrace();
}

return result;
}
}

该实现类实际上就是在方法内部执行MyBatis的上述步骤,直接给调用者返回结果,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class AccessDB {
public static void main(String[] args) {
// 创建实现类对象(接口对象)
UserDao dao = new UserDaoImpl();

User user = new User(13, "lisi", "lisi456", "llss");
// 实现类对象调用方法,直接获得结果
int i = dao.insertUser(user);
System.out.println(i);

// 实现类对象调用方法,直接获得结果
List<User> users = dao.selectUsers();
Iterator<User> iterator = users.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}

可以看到,这样做才是最常规的做法,但是这样做有没有缺点呢?

最基本的缺点就是,我们新增一个数据库,除了需要新增一个对应的实体类、以及接口 DAO外,还需要新增接口的实现类。

我们知道,新增一个数据库的话,在程序中新增实体类以及DAO接口这是必须的,那么这个DAO接口的实现类有必要写吗?实际上是没必要的,可以利用动态代理来创建该类。该类的本质就是调用映射文件中的特定的SQL语句。

  1. 如果通过动态代理创建实现类,那么怎么获取该类(要实现的接口)的名字呢?

    答案是传参,将接口DAO的class传进去。

  2. 如何在实现类内部,将 namespace 和 id 传入呢?

    这里其实很简单,将SQL映射文件中 namespace的值 设为 所映射接口的全限定名称即可(这也就是之前为什么建议这样做)。

    那么id怎么传入呢?我们知道,SQL映射文件中的标签已经和方法对应起来了,而标签和id是一一对应的,所以当我们调用方法的时候,MyBatis就可以定位到接口的方法,然后就可以定位到SQL映射文件的标签,实际上就相当于定位到id了。所以一般情况下,Dao接口不建议写重载方法。

  3. 此时,就可以创建该代理类,并实现方法的调用了。

  4. 总结:

    1. namespace值要和Dao接口全限定名称一致。
    2. id值要和对应Dao接口中的方法名一致。

需要用到SqlSession的getMapper()方法

1
<T> T getMapper(Class<T> var1)

代码如下所示(此时不再需要实现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
public class AccessDB {
public static void main(String[] args) {

try {
SqlSession sqlSession = MyBatisUtils.getSqlSession();

// 获取动态代理实现的类对象
UserDao mapper = sqlSession.getMapper(UserDao.class);

// System.out.println(mapper.getClass().getName());

User user = new User(14, "wagnwu", "wangwu789", "ww");

// 直接获得结果
int i = mapper.insertUser(user);
sqlSession.commit();
System.out.println(i);

// 直接获得结果
List<User> users = mapper.selectUsers();
Iterator<User> iterator = users.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}

sqlSession.close();

} catch (IOException e) {
e.printStackTrace();
}
}
}

注意,在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
2
3
<select id="selectUsersByNo" parameterType="java.lang.Integer" resultType="org.hianian.entity.User">
select no, login, password, name from t_user where no=#{no} order by no
</select>

3.2.2 传入一个简单类型参数

简单类型参数指的是基本数据类型(包括其对应的包装类型)。

我们知道,MyBatis会将接口中的方法和SQL映射文件一一对应起来,也就是将抽象方法和具体的内部实现一一对应起来。那么此时如何传入参数呢?

接口中的抽象方法的参数还是按照原来的方式写,即在方法参数括号中写参数;在SQL映射文件中,采用 #{任意字符} 的形式来接收数据。即表示SQL语句中的占位符。简单案例如下所示:

1
2
// Dao接口中的抽象方法
public User selectUsersByNo(int no);
1
2
3
<select id="selectUsersByNo" parameterType="java.lang.Integer" resultType="org.hianian.entity.User">
select no, login, password, name from t_user where no=#{no} order by no
</select>

cybersecurity.gitee.io/blog-image-bed/JavaWeb/mybatis/mybatis_006.png

可以看到,接收参数的时候,和JDBC中的PreparedStatement对象预编译那样,将SQL语句预先编译,保留占位符,然后再接收参数并拼接。

实际上和JDBC中的步骤一样:

  1. 创建Connection连接对象,PreparedStatement对象,预编译SQL语句,接收参数。
  2. 执行SQL语句封装为resultType指定的数据类型对象。进一步将这[些]对象封装成对应函数的返回结果。

3.2.3 传入多个参数——使用@Param(重点)

这种方法也被称为命名参数。上面的方法中是在SQL映射文件中进行设置,而Dao接口中不做改变。而本方法则是在Dao接口中的形参前面添加注解@Param("自定义参数"),在SQL映射文件中依然采用#{自定义参数}来接收参数,注意这两个自定义参数要一致。

我们知道,上面的方法只传了一个参数,所以在SQL映射文件中可以自定义变量,因为只有一个,无论自定义的是什么,肯定就是为了接受方法中唯一的那个参数。

而这里需要传入多个参数,这时候该怎么对应起来呢?本方法就是在Dao接口中的方法的参数添加注解,相当于告诉MyBatis程序中的位置,然后再SQL映射文件中,自定义的参数名称为该注解中的内容即可。简单案例如下所示:

1
2
// Dao接口中的抽象方法
public User selectUsersByParam(@Param("myNo") int no, @Param("myName") String name);
1
2
3
4
<!-- 映射文件中的标签 -->
<select id="selectUsersByParam" resultType="org.hianian.entity.User">
select no, login, password, name from t_user where no=#{myNo} and name=#{myName} order by no
</select>

结果如下所示:

mybatis_007.png (1119×325) (gitee.io)

3.2.4 传入多个参数——使用对象(重点)

有时候参数太多,在接口中形参太多就不太方便了,这时候可以将这些参数封装成一个对象,直接传入对象。

这种方法其实在前面2.3插入案例中已经用到了,方法的参数是User对象,而在SQL映射文件中则是用#{对象属性名, javaType=类型名称, jdbcType=数据类型}来接收值。

注意,这是完整的用法,其中javaType用来指明这个参数在Java程序中的数据类型,jdbcType用来指明这个参数在数据库中的数据类型。这两种数据类型的具体名称都在MyBatis中指明了,可以查阅文档。

但是其实这两种数据类型实际上通过反射机制是可以获取到的,此时我们不需要在手动指明,所以这两个可以省略。简化方法为#{对象属性名}

案例如下所示:

1
public int insertUser(User user);
1
2
3
<insert id="insertUser">
insert into t_user values(#{no}, #{login}, #{password}, #{name})
</insert>

注意,在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
2
3
<select id="selectUsersByIndex" resultType="org.hianian.entity.User">
select no, login, password, name from t_user where no=#{arg0} and name=#{arg1} order by no
</select>

3.2.6 传入多个参数——使用Map(了解)

还有一种方式采用Map<String, Object>传参,将Map作为抽象方法的形参传入,在SQL映射文件中,采用Map中的key作为参数来接收对应的value,即#{key}

简单案例如下所示:

1
public User selectUsersByMap(Map<String, Object> map);
1
2
3
<select id="selectUsersByMap" resultType="org.hianian.entity.User">
select no, login, password, name from t_user where no=#{no} and name=#{name} order by no
</select>
1
2
3
4
5
6
Map<String, Object> map = new HashMap<>();
map.put("no", 15);
map.put("name", "ww");

User user = mapper.selectUsersByMap(map);
System.out.println("user=" + user);

不建议使用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
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
public class User {

private int no;
private String logins;

public int getNo() {
return no;
}

public void setNo(int no) {
this.no = no;
}

public String getLogins() {
return logins;
}

public void setLogins(String logins) {
this.logins = logins;
}

@Override
public String toString() {
return "User{" +
"no=" + no +
", logins='" + logins + '\'' +
'}';
}
}

大致的处理步骤如下所示:

  1. MyBatis执行SQL语句,然后MyBatis通过该标签获得class类,调用该类的无参构造方法,创建对象。(所以Entity实体类必须有无参构造方法

  2. MyBatis把执行结果ResultSet结果中的列值赋值给类对象中同名的属性。(所以Entity实体类必须要有set、get方法)(注意,是调用,会调用set字段名方法给对应字段的属性赋值。如果没有,则不赋值

    mybatis_008.png (1053×322) (gitee.io)

    如上图所示,User对象有两个属性:no和logins,但是四个字段只有no和User对象一样,所以只给User对象中no赋值,其他属性为null。

    mybatis_009.png (1063×310) (gitee.io)

    另外,如果起了别名,也是可以的,总体来说,就是将查询结果中的字段赋值给resultType类型对象的对应属性值上。

  3. 之后,通过反射获取该Dao接口中方法的返回值,如果该方法的返回值,如果就是该对象类型,则直接返回;如果是List等集合,那么就将上面转换成的多个对象进一步封装成集合,返回给程序。

前面提到过,parameterType可以采用MyBatis定义的别名来代替Java中的类型,这里resultType同样可以使用MyBatis中的别名。另外,MyBatis中都是Java自带类型的别名,此时我们可以在MyBatis主配置文件中为我们自定义的类型起别名,如将org.hianian.entity.User起别名为entityUser。在MyBatis主配置文件中设置如下:

1
2
3
4
<typeAliases>
<!-- type是原始类名,alias是自定义的别名。这种方法语法比较复杂 -->
<typeAlias type="org.hianian.entity.User" alias="entityUser"></typeAlias>
</typeAliases>

除此之外,还有另一种方式:

1
2
3
4
<typeAliases>
<!-- name包下的所有类的类名就是该类的别名,即直接使用类名作为别名,可以不用写包名了。从另一种角度解释就是说,相当于提前声明了包,提前引用了包,类名可以直接使用。这种方法不允许不同的包下有同类型类 -->
<package name="org.hianian.entity"></package>
</typeAliases>

上面设置的是返回值类型为引用数据类型:org.hianian.entity.User,是将查询结果的字段名赋值给对象中同名的属性;

除此之外,还可以将返回值类型赋值给java.lang.Integer等类型,这种是适用于返回值为整数型的接口方法。

还可以赋值给java.util.HashMap类型,即<字段=值, 字段=值[, …]>类似这种的。

3.3.2 resultMap(常用)

resultMap指的是结果映射,用来指定列名和Java对象的属性之间的对应关系。用法主要如下:

  1. 自定义列赋值给Java对象的哪个属性(不再是同名对应关系)
  2. 当你的列名和属性名不一样时,可以用resultMap来设置。

resultMap是和<select>等标签同级的标签。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- 定义resultMap,id是自定义名称,用来标识resultMap;
type是Java类的全限定名称,即用来映射哪个Java类对象
-->

<resultMap id="UserMap" type="org.hianian.entity.User">

<!--定义SQL语句执行结果中列名和Java类属性的关系
主键列使用id标签,其中column属性指的是列名,property指的是Java类属性名
-->
<id column="id" property="ids"></id>

<!-- 定义非主键列,使用result标签 -->
<result column="login" property="logins"></result>
<result column="password" property="passwords"></result>
</resultMap>

<!-- 在select标签中,使用resultMap来定义返回封装的对象,不再使用resultType -->
<select id="selectUsersByMap" resultMap="UserMap">
select no, login, password, name from t_user where no=#{no} and name=#{name} order by no
</select>

测试案例如下所示:

1
2
3
4
5
6
7
<resultMap id="UserMap" type="org.hianian.entity.User">
<id column="no" property="no"></id>
<result column="login" property="logins"></result>
</resultMap>
<select id="selectAllUsers" resultMap="UserMap">
select no, login, password, name from t_user
</select>

mybatis_010.png (438×504) (gitee.io)

3.3.3 实体类属性名和列名不同的处理方式

通过上述讲解,实体类属性名和列名不一致的处理方式主要有以下几种:

  1. resultMap映射
  2. SQL语句起别名,别名和属性名一致

3.4 模糊查询like

这里讲解一下模糊查询的例子,主要有两种实现方法:

  1. 将like后面的内容作为参数,传入到SQL映射文件中,即"%李%";SQL中接收select * from t_user where name like #{name}。(推荐)
  2. 将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
2
3
4
5
6
7
8
9
10
11
<select id="selectUserIf" resultType="org.hianian.entity.User">
select id, name, age, email from User where
<if test="name != null and name !=''">
name = #{name}
</if>
<if test="age > 0">
and age > #{age}
</if>
</select>

<!-- 上面的语句就是如果有name条件,并且不为空以及不是空字符串,就将其作为条件拼接到SQL语句中;如果有年龄,则将年龄拼接到SQL语句中。注意SQL语句中的连接词and -->

但是上面的语句明显有问题,如果第一个条件不满足,第二条件满足,那么就会在where后面直接出现了and关键字,就会出现了SQL语法错误。所以,应该使得后面的条件不应该总体的语法。如下面所示:

1
2
3
4
5
6
7
8
9
<select id="selectUserIf" resultType="org.hianian.entity.User">
select id, name, age, email from User where 1=1
<if test="name != null and name !=''">
and name = #{name}
</if>
<if test="age > 0">
and age > #{age}
</if>
</select>

即在where条件后先添加一个永为真的条件,后续的条件前面直接带上and、or等连接词。不过这种只是解决if标签的一种方式,下面的<where>标签可以完全解决这个问题。

4.2 动态SQL之<where>

where标签用来包含多个<if>标签,当多个if有一个成立的,where标签会自动增加一个where关键字,并去掉if中多余的and、or等关键字。

1
2
3
4
5
6
<!-- where标签语法 -->
<where>
<if></if>
<if></if>
...
</where>

案例如下所示,不需要单独再考虑条件以及关键字and、or:

1
2
3
4
5
6
7
8
9
10
11
<select id="selectUserIf" resultType="org.hianian.entity.User">
select id, name, age, email from User
<where>
<if test="name != null and name !=''">
name = #{name}
</if>
<if test="age > 0">
and age > #{age}
</if>
</where>
</select>

4.3 动态SQL之<foreach>

foreach标签用于实现对于数组与集合的遍历,主要用在SQL中的in语句中,比如select * from student where id in (1001, 1002, 1003)这种SQL语句,参数1001,1002,1003怎么提供呢?SQL映射文件怎么获取呢?(注意,这是in条件可能存在可能没有)所以采用foreach标签。

对其使用,需要注意:

  1. collection表示要遍历的集合类型,如list、array等。
  2. open、close、separator为对遍历内容的SQL拼接。

语法如下所示:

1
2
3
<foreach collection="集合类型" open="开始的字符" close="结束的字符" item="集合中的成员,自定义" separator="集合成员之间的分隔符">
#{item的值}
</foreach>

案例如下所示:

1
2
3
4
5
6
<select id="selectUsersByForeach" resultType="org.hianian.entity.User">
select no, login, password, name from t_user where no in
<foreach collection="list" item="user" open="(" close=")" separator=",">
#{user.no}
</foreach>
</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
@Test
public void testSelectUsersByForeach(){

try {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);

List<User> list = new ArrayList<>();

list.add(new User(1, null, null, null));
list.add(new User(5, null, null, null));
list.add(new User(3, null, null, null));
list.add(new User(7, null, null, null));


List<User> users = userDao.selectUsersByForeach(list);
Iterator<User> iterator = users.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}


} catch (IOException e) {
e.printStackTrace();
}
}

另一个案例如下:

1
2
3
4
5
6
<select id="selectUsersByForeach2" resultType="org.hianian.entity.User">
select no, login, password, name from t_user where no in
<foreach collection="array" item="num" open="(" close=")" separator=",">
#{num}
</foreach>
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
public void testSelectUsersByForeach2(){

try {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);

int[] ints = {1, 2, 9, 10};


List<User> users = userDao.selectUsersByForeach2(ints);
Iterator<User> iterator = users.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}

} catch (IOException e) {
e.printStackTrace();
}
}

foreach中的集合类型里面的元素,可以是基本数据类型,也可以是引用数据类型。对于基本数据类型,接收值的时候,直接#{item值}即可;对于引用数据类型,接收值的时候,直接#{item值.对象属性}来获取值。

另外,open和close可以省略,自己在SQL语句中拼接。

这里foreach相当于实现了可变参数传参

4.4 动态SQL之代码片段

代码片段是复用代码的一种方式。<sql>标签用于定义SQL片段,以便其他SQL标签复用。而其他标签使用该SQL片段,需要使用<include>字标签。该<sql>标签可以定义SQL语句中的任何部分,所以<include>字标签可以放在动态SQL中的任何位置。

用法如下:

1
2
3
<sql id="唯一表示">
SQL代码片段
</sql>

调用语法如下(以select标签为例):

1
2
3
4
<select>
<include refid="上述的唯一表示"></include>
其他SQL代码片段
</select>

案例如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
<sql id="selectAll">
select no, login, password, name from t_user
</sql>

<select id="selectUsersByIf" resultType="org.hianian.entity.User">
<include refid="selectAll"></include> where 1=1
<if test="name != null and name !=''">
and name = #{name}
</if>
<if test="no > 10">
and no > #{no}
</if>
</select>

5. MyBatis配置文件

这部分内容讲解一下配置文件中的相关知识。

5.1 主配置文件

主配置文件中的大部分内容在上面已经陈述了,并且其余的内容基本上采用默认值即可。至于datasource、transactionManager等标签如下面小节所示。

5.2 dataSource标签

<dataSource type="POOLED">标签表示数据源,即连接数据库的相关部分,实现了javax.sql.DataSource接口,用于创建连接对象。

  1. type属性为POOLED指明了创建多个连接对象,形成连接池;
  2. type属性为UNPOOLED表示不使用连接池;
  3. type属性为JNDI,表示Java命名和目录服务(windows注册表),了解。

5.3 事务

<transactionManager type="JDBC"/>标签表示提交事务回滚事务的方式。该标签type属性的值有两个:

  1. JDBC:表示MyBatis底层是调用JDBC中的Connection对象的commit、rollback等来进行事务的相关操作。
  2. MANAGED:表示把MyBatis的事务处理委托给其他的容器(比如一个服务器软件、一个框架(sping)等等)

5.4 使用数据库属性配置文件(重点

从上面可以看到,数据库的相关配置信息是在主配置文件中。如果有多个数据库,可以直接写多个数据库配置信息。但是这样做不方便修改和保存。因此可以将数据库连接信息放到一个单独的文件中,和MyBatis主配置文件分开,这就是数据库的属性配置文件目的是便于修改、保存,处理多个数据库的信息。步骤如下所示:

  1. 在resources目录中定义一个属性配置文件:xxx.proeprties,如jdbc.properties。在属性配置文件中定义数据,格式是:key=value
  2. 在mybatis主配置文件中,使用<properties>标签指定文件的位置,在需要使用值的地方,使用${key}获取对应的value即可。

这时候如果想要修改数据库连接方面的内容,直接在专属的属性配置文件中修改即可,比较直观,不易犯错。

配置文件如下所示:

1
2
3
4
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://ip:port/mysql_test
jdbc.username=root
jdbc.password=root

主配置文件如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
<properties resource="jdbc.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.driver}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>

5.5 typeAliases(类型别名)

别名如前面所示,这里不再赘述。

5.6 mappers(映射器,重点)

mapper标签用于指定映射文件,一个标签只能指定一个文件,如果有多个映射文件,此时就需要有多个mapper标签。但是这样做很不方便,因为大型项目中会有很多实体类,并且都有对应的Dao接口操作,这样逐个添加不好管理。

因此可使用另一种方式(重点):使用包名,即<package name=”mapper文件xml所在的包名”>。和3.3.1上面的别名一样,相当于把包名导入,即里面的所有xml文件都导入了进来。注意,该package标签仍然是在mappers标签里面使用。并且有以下几点要求

  1. mapper文件名称需要和接口名称一样,且区分大小写。
  2. mapper文件和dao接口需要在同一目录下。

这也就是为什么在前面建议名称一致,且在同一目录下,就是为了方便这里统一映射。

6. 扩展

6.1 分页

我们知道MySQL中有分页查询操作,基于limit关键字可以实现。但是为了方便快捷,某作者出现了一个小工具PageHelper,可以帮我们在MyBatis中实现limit操作,并且支持许多主流的数据库。

这里不再赘述,网络上检索一些帮助文档即可:MyBatis 分页插件 PageHelper

6.2 逆向工程

前面我们讲的是,利用MyBatis来访问数据库,即需要编写实体类、Dao、以及映射文件等等。其实仔细想想,实体类对象对应的就是一条记录,Dao对应的就是具体的SQL操作,映射文件则是具体的SQL语句。其实这些都是固定的,完全可以利用程序来实现上面的三部分,这就是MyBatis逆向工程。案例如下所示:

  1. 新建项目(普通项目即可)

  2. 添加插件:(注意,这是插件,不是依赖。需要放在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>
  3. 添加配置文件,告诉插件如下信息:

    1. 数据库连接信息

      1
      2
      3
      4
      5
      jdbc.driverLocation=D:/profession/maven/maven_repository/mysql/mysql-connector-java/8.0.28/mysql-connector-java-8.0.28.jar
      jdbc.driverClass=com.mysql.cj.jdbc.Driver
      jdbc.connectionURL=jdbc:mysql://ip:port/crm2022?serverTimezone=UTC&characterEncoding=utf-8
      jdbc.userId=crm2022root
      jdbc.userPassword=crm2022password
    2. 数据表的信息

    3. 生成后的代码保存的目录

      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
      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE generatorConfiguration
      PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
      "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

      <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>

生成后的文件如下所示:

image-20220630145631008

7. 总结

MyBatis是JDBC的升级版,将数据库操作和代码部分分离。所以以前代码中的数据库操作部分有:

  1. JDBC驱动、连接对象等等;
  2. Dao接口的实现类
  3. SQL语句PreparedStatement对象执行
  4. SQL语句接收参数
  5. SQL执行结果封装成实体类或者其他对象

都会被映射为MyBatis操作:

  1. MyBatis主配置文件
  2. SqlSession.getMapper(),反射创建实现类,Dao代理
  3. MyBatis的SQL映射文件
  4. parameterType属性、#{类属性名}@Param接收参数
  5. resultType、resultMap将结果封装成对应的Java对象

另外,还有动态SQL以及配置文件的相关用法。

8. 备注

参考B站《动力节点》。


文章作者: 浮云
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 浮云 !
  目录