Mybatis笔记

Mybatis

1. JDBC操作分析

// 数据库连接
Connection connection = null;
// 预处理对象
PreparedStatement preparedStatement = null;
// 查询结果集
ResultSet resultSet = null;
try {
    // 1.加载驱动
    Class.forName("com.mysql.jdbc.Driver");

    // 2.获取连接
    connection = DriverManager.getConnection("jdbc:mysql://luojie.site:33306/demo",
                                             "root",
                                             "123456");

    // 定义执行SQL
    String sql = "select user_id,user_no,user_name from users where user_name = ?";
    // 3.获取预处理statement,设置参数
    preparedStatement = connection.prepareStatement(sql);
    preparedStatement.setString(1, "MySQL");

    // 4.执行SQL,获取结果集
    resultSet = preparedStatement.executeQuery();

    // 5.遍历结果集,封装数据
    User user = null;
    while (resultSet.next()) {
        user = new User();
        int userId = resultSet.getInt("user_id");
        String userNo = resultSet.getString("user_no");
        String userName = resultSet.getString("user_name");
        user.setUserId(userId);
        user.setUserNo(userNo);
        user.setUserName(userName);
    }
    System.out.println(user);
} catch (Exception e) {
    e.printStackTrace();
} finally {
    if (null != connection) {
        try {
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    if (null != preparedStatement) {
        try {
            preparedStatement.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    if (null != resultSet) {
        try {
            resultSet.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

1、频繁创建、释放数据库连接,影响系统性能。

2、SQL硬编码、不易维护。

3、SQL参数硬编码,SQL参数异变,不易维护。

4、对获取数据结果集硬编码,SQL变更,获取数据结果集需要便跟,系统不易维护。

2. MyBatis 是什么?

2.1 对象/关系映射(ORM)

ORM 全称 Object Relation Mapping :表示对象-关系映射的缩写

ORM 完成面向对象的编程语言到关系数据库的映射。当ORM框架完成映射后,程序员既可以利用面向对象程序设计语言的简单易用性,又可以利用关系数据库的技术优势。ORM把关系数据库包装面向对象的模型。ORM框架是面向对象设计语言与关系数据库发展不同步时的中间解决方案。采用ORM框架后应用程序不在直接访问底层数据库,而是以面向对象的方式来操作持久化对象,而ORM框架则将这些面向对象操作转换为底层SQL操作。ORM框架实现的效果:把对持久化对象的保存、修改、删除等操作,转换为对数据库的操作。

2.2 Mybatis 简介?

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

2.3 Mybatis历史

  1. 原是Apache的一个开源项目iBatis,2010年6月这个项目由Apache Software Foundation 迁移到了Google Code,随着开发团队转投Google Code旗下,iBatis3.x正式更名为Mybatis,代码与2013年11月迁移到Github。

    Mybatis 官网:https://mybatis.org/mybatis-3/zh/index.html
    MyBatis 源码:https://github.com/mybatis/mybatis-3

  2. IBatis一词源于 internet 和 abatis 的组合,是一个基于java的持久层框架。

2.4 Mybatis优势

    Mybatis 是一个半自动的持久层框架,对应开发人员而言,核心的sql还是需要自己优化,sql和java编码隔离,功能边界清晰,一个专注业务,一个专注数据。

分析图如下:

image-20200611091841470.png

3. Mybatis 使用与说明

3.1 Mybatis 简单使用

3.1.1 快速入门

Mybatis开发步骤

1)添加Mybatis坐标

    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.3</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

2)创建user数据表

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(225) DEFAULT NULL,
  `password` varchar(225) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;


INSERT INTO `demo`.`user` (`id`, `username`, `password`) VALUES ('1', '张三', '123456');
INSERT INTO `demo`.`user` (`id`, `username`, `password`) VALUES ('2', '李四', '456789');
INSERT INTO `demo`.`user` (`id`, `username`, `password`) VALUES ('3', '王五', '987654');

3)编写User实体类

@Data
public class User {
    private Integer id;
    private String username;
    private String password;
}

4)编写映射文件UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ltgx.mapper.UserMapper">
</mapper>

5)编写核心文件SqlMapConfig.xml

<?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://luojie.site:33306/demo"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>
</configuration>

6)编写测试类

    // 查询所有用户
    @Test
    public void testFindAll() throws IOException {

        // 加载核心配置文件
        InputStream resourceAsStream =
                Resources.getResourceAsStream("SqlMapConfig.xml");
        // 获取SqlSession 工厂
        SqlSessionFactory sqlSessionFactory = new
                SqlSessionFactoryBuilder().build(resourceAsStream);
        // 获取SqlSession 连接
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 对应操作
        
        // 关闭连接
        sqlSession.close();
    }

3.1.2 增删改查操作

3.1.2 增操作

1)编写UserMapper.xml

<mapper namespace="com.ltgx.mapper.UserMapper">
    <insert id="insertUser" parameterType="com.ltgx.entity.User">
        insert into user (username,password) value(#{username},#{password})
    </insert>
</mapper>

2)插入User代码

    @Test
    public void testInsertUser() throws IOException {
        // 加载核心配置文件
        InputStream resourceAsStream =
                Resources.getResourceAsStream("SqlMapConfig.xml");
        // 获取SqlSession 工厂
        SqlSessionFactory sqlSessionFactory = new
                SqlSessionFactoryBuilder().build(resourceAsStream);
        // 获取SqlSession 连接
        SqlSession sqlSession = sqlSessionFactory.openSession();
        User user = new User();
        user.setUsername("测试");
        user.setPassword("123456");
        // 插入操作
        int insert = sqlSession.insert("com.ltgx.mapper.UserMapper.insertUser", user);
        sqlSession.commit();
        // 打印结果数据
        System.out.println(insert);
        // 关闭连接
        sqlSession.close();
    }

注意事项

新增使用insert标签

在映射文件中使用parameterType属性指定要插入的数据类型

sql语句中使用#{实体属性名}方式引入实体中的值

插入操作使用的API是sqlSession.insert("命名空间.id",对象)

插入操作涉及数据库数据变化,所以要使用sqlSession对象显示的提交事务,即sqlSession.commit()

3.1.2 删操作

1)编写UserMapper.xml

<mapper namespace="com.ltgx.mapper.UserMapper">
    <delete id="deleteUser" parameterType="java.lang.Integer">
        delete from user where id = #{id}
    </delete>
</mapper>

2)删除User代码

    @Test
    public void testDeleteUser() throws IOException {
        // 加载核心配置文件
        InputStream resourceAsStream =
                Resources.getResourceAsStream("SqlMapConfig.xml");
        // 获取SqlSession 工厂
        SqlSessionFactory sqlSessionFactory = new
                SqlSessionFactoryBuilder().build(resourceAsStream);
        // 获取SqlSession 连接
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 操作
        int delete = sqlSession.delete("com.ltgx.mapper.UserMapper.deleteUser", 4);
        sqlSession.commit();
        // 打印结果数据
        System.out.println(delete);
        // 关闭连接
        sqlSession.close();
    }

注意事项

删除使用delete标签

在映射文件中使用parameterType属性指定要修改的数据类型

sql语句中使用#{任意字符串}方式引入传递的单个参数

删除操作使用的API是sqlSession.delete("命名空间.id",参数)

删除操作涉及数据库数据变化,所以要使用sqlSession对象显示的提交事务,即sqlSession.commit()

3.1.2 改操作

1)编写UserMapper.xml

<mapper namespace="com.ltgx.mapper.UserMapper">
    <update id="updateUser" parameterType="com.ltgx.entity.User">
        update user set username=#{username},password=#{password} where id =#{id}
    </update>
</mapper>

2)修改User代码

    @Test
    public void testUpdateUser() throws IOException {
        // 加载核心配置文件
        InputStream resourceAsStream =
                Resources.getResourceAsStream("SqlMapConfig.xml");
        // 获取SqlSession 工厂
        SqlSessionFactory sqlSessionFactory = new
                SqlSessionFactoryBuilder().build(resourceAsStream);
        // 获取SqlSession 连接
        SqlSession sqlSession = sqlSessionFactory.openSession();
        User user = new User();
        user.setId(2);
        user.setUsername("测试");
        user.setPassword("123456");
        // 操作
        int update = sqlSession.update("com.ltgx.mapper.UserMapper.updateUser", user);
        sqlSession.commit();
        // 打印结果数据
        System.out.println(update);
        // 关闭连接
        sqlSession.close();
    }

注意事项

修改使用update标签

在映射文件中使用parameterType属性指定要修改的数据类型

sql语句中使用#{实体属性名}方式引入实体中的值

修改操作使用的API是sqlSession.insert("命名空间.id",对象)

修改操作涉及数据库数据变化,所以要使用sqlSession对象显示的提交事务,即sqlSession.commit()

3.1.2 查操作

1)编写UserMapper.xml

<mapper namespace="com.ltgx.mapper.UserMapper">
    <select id="findAll" resultType="com.ltgx.entity.User">
        select * from user
    </select>
</mapper>

2)修改User代码

    @Test
    public void testFindAll() throws IOException {
        // 加载核心配置文件
        InputStream resourceAsStream =
                Resources.getResourceAsStream("SqlMapConfig.xml");
        // 获取SqlSession 工厂
        SqlSessionFactory sqlSessionFactory = new
                SqlSessionFactoryBuilder().build(resourceAsStream);
        // 获取SqlSession 连接
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 查询所有数据
        List<User> userList = sqlSession.selectList("com.ltgx.mapper.UserMapper.findAll");
        // 打印结果数据
        System.out.println(userList);
        // 关闭连接
        sqlSession.close();
    }

注意事项

查询使用select标签

在映射文件中使用resultType属性指定要返回的数据类型

查询操作使用的API是sqlSession.selectXXX("命名空间.id")

3.1.3 使用方式

3.1.3.1 传统DAO方式

1)编写DAO接口

public interface UserDao {

    List<User> findAll() throws IOException;

}

2)编写DAO接口实现

public class UserDaoImpl implements UserDao {

    @Override
    public List<User> findAll() throws IOException {
        // 加载核心配置文件
        InputStream resourceAsStream =
                Resources.getResourceAsStream("SqlMapConfig.xml");
        // 获取SqlSession 工厂
        SqlSessionFactory sqlSessionFactory = new
                SqlSessionFactoryBuilder().build(resourceAsStream);
        // 获取SqlSession 连接
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 查询所有数据
        List<User> userList = sqlSession.selectList("com.ltgx.mapper.UserMapper.findAll");
        // 打印结果数据
        System.out.println(userList);
        // 关闭连接
        sqlSession.close();
        return userList;
    }
}

3)测试方式

    @Test
    public void test() throws IOException {
        UserDao userDao = new UserDaoImpl();
        List<User> all = userDao.findAll();
        System.out.println(all);
    }
3.1.3.2 代理方式

代理开发方式采用Mybatis的代理开发方式实现DAO层的开发

Mapper 接口开发方法只需要编写Mapper接口(相当于DAO的接口),由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法同上边DAO接口实现类的方法。

Mapper接口开发遵循以下规范:

1)Mapper.xml文件中的namespace与Mapper接口的全限定类名相同

2)Mapper接口方法与Mapper.xml中定义的每个statement的id相同

3)Mapper接口方法的输入参数类型和Mapper.xml中定义的每个SQL的parameterType的类型相同

4)Mapper接口方法的输出参数类型和Mapper.xml中定义的每个SQL的resultTYPE的类型相同

编写Mapper接口代码

public interface UserMapper {

    List<User> findAll();

}

测试代码

    @Test
    public void testMapperFindAll() throws IOException {
        // 加载核心配置文件
        InputStream resourceAsStream =
                Resources.getResourceAsStream("SqlMapConfig.xml");
        // 获取SqlSession 工厂
        SqlSessionFactory sqlSessionFactory = new
                SqlSessionFactoryBuilder().build(resourceAsStream);
        // 获取SqlSession 连接
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 获取 Mapper
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        // 查询所有数据
        List<User> users = mapper.findAll();
        // 打印结果数据
        System.out.println(users);
        // 关闭连接
        sqlSession.close();
    }

3.2 配置文件

3.2.1 SqlMapConfig.xml

MyBatis 核心配置文件,其核心配置文件层级关系

[图片上传失败...(image-fd2856-1613610185688)]

下面为mybatis常用配置解析

3.2.1.1 environments 标签
<?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">
            <!-- 指定当前事务类型为JDBC -->
            <transactionManager type="JDBC"/>
            <!-- 指定当前数据源类型为连接池 -->
            <dataSource type="POOLED">
                <!-- 数据源的基本配置 -->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://luojie.site:33306/demo"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

事务管理器(transactionManager)类型有两种:

  • JDBC:这个配置就是直接使用了JDBC的提交和回滚设置,它依赖于从数据源得到连接来管理事务作用域
  • MANAGED :这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。

数据源(dataSource)类型有三种:

  • UNPOOLED:这个数据源的实现只是每次请求时打开和关闭连接。
  • POOLED:这种数据源的实现利用“池”的概念将JDBC连接对象组织起来。
  • JNDI:这个数据源的实现是为了能在如EJB或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个JNDI上下文的引用。
3.2.1.2 mapper 标签

该标签的作用是加载映射的,加载方式有如下几种:

  • 相对类路径的资源引用

    <mappers>
      <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
      <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
      <mapper resource="org/mybatis/builder/PostMapper.xml"/>
    </mappers>
    
  • 使用完全限定资源定位符(URL)

    <!-- 使用完全限定资源定位符(URL) -->
    <mappers>
      <mapper url="file:///var/mappers/AuthorMapper.xml"/>
      <mapper url="file:///var/mappers/BlogMapper.xml"/>
      <mapper url="file:///var/mappers/PostMapper.xml"/>
    </mappers>
    
  • 使用映射器接口实现类的完全限定类名

    <!-- 使用映射器接口实现类的完全限定类名 -->
    <mappers>
      <mapper class="org.mybatis.builder.AuthorMapper"/>
      <mapper class="org.mybatis.builder.BlogMapper"/>
      <mapper class="org.mybatis.builder.PostMapper"/>
    </mappers>
    
  • 将包内的映射器接口实现全部注册为映射器

    <!-- 将包内的映射器接口实现全部注册为映射器 -->
    <mappers>
      <package name="org.mybatis.builder"/>
    </mappers>
    
3.2.1.3 properties 标签

习惯将数据源的配置信息单独抽取成一个properties文件,该标签可以加载额外配置的properties文件

image-20200611110632054.png
3.2.1.4 typeAliases 标签

类型别名是为java类型设置的一个短的名字

image-20200611110941593.png

上面是我们自定义的别名,下面是mybatis框架已经为我们定义好的一些常用的类型的别名

别名 映射的类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator
3.2.1.5 settings 标签

MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为

设置名 描述 有效值 默认值
cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 true|false true
mapUnderscoreToCamelCase 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 true|false false

3.2.2 mapper.xml

mapper.xml 对象与关系映射的具体文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ltgx.mapper.UserMapper">
</mapper>

在mybatis中每条SQL的唯一标识通过【namespace.id】mapper的namespace命名空间名+“.”+id组成。

3.2.2.1 select 标签

select 映射查询语句,用于数据查询,从数据库取出数据。

<?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.ltgx.mapper.UserMapper">

    <resultMap id="userResultMap" type="com.ltgx.entity.User">
        <id column="uid" property="id"/>
        <result column="username" property="username"/>
        <result column="password" property="password"/>
    </resultMap>

    <select id="findAllResultMap" resultMap="userResultMap">
        select id,username,password from user
    </select>

    <select id="findAllResultType" resultType="com.ltgx.entity.User">
        select id,username,password from user
    </select>

</mapper>

同时 select 标签提供了很多属性来配置每条语句的行为细节

<select
  id="selectPerson"
  parameterType="int"
  resultType="hashmap"
  resultMap="personResultMap"
  flushCache="false"
  useCache="true"
  timeout="10"
  fetchSize="256"
  statementType="PREPARED"
  resultSetType="FORWARD_ONLY">

其常用属性说明

属性 描述
id 在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
resultType 期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。
resultMap 对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。
3.2.2.2 insert、delete、update 标签
<insert
  id="insertAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  keyProperty=""
  keyColumn=""
  useGeneratedKeys=""
  timeout="20">

<update
  id="updateAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">

<delete
  id="deleteAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">
属性 描述
id 在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
useGeneratedKeys (仅适用于 insert 和 update)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。
keyProperty (仅适用于 insert 和 update)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset)。如果生成列不止一个,可以用逗号分隔多个属性名称。

插入时会写ID

    <insert id="insertUserGeneratedKey" parameterType="com.ltgx.entity.User" useGeneratedKeys="true" keyProperty="id">
        insert into user (username,password) value (#{username},#{password})
    </insert>
3.2.2.3 结果映射

resultMap、resultType 进行结果集映射。 resultType 和 resultMap 之间只能同时使用一个。

进行结果集映射

    <resultMap id="userResultMap" type="com.ltgx.entity.User">
        <id column="id" property="id"/>
        <result column="username" property="username"/>
        <result column="password" property="password"/>
    </resultMap>

    <select id="findAllResultMap" resultMap="userResultMap">
        select id,username,password from user
    </select>

    <select id="findAllResultType" resultType="com.ltgx.entity.User">
        select id,username,password from user
    </select>
@Data
public class User {
    private Integer id;
    private String username;
    private String password;
}

resultMap:指定映射关系

resultType:使用 JavaBean 或 POJO 进行结果集映射,映射时需要查询的列明与字段名一致(mapUnderscoreToCamelCase-是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn)

3.2.2.4 参数

{} 、${}

一般情况下使用 #{参数名} 进行参数传递。

一个参数的情况下,#{参数名} 参数名可以随便写,为了提高可维护性、可读性,最好与参数名一致。

多个参数的情况下,#{参数名} 需要与传入的参数名进行对应。如果传入的是 HashMap 的情况,参数名为HashMap的key。如果传入的参数为 POJO 对象,需要与 POJO 对象的字段名一致。

${} : 进行字符串替换

直接在 SQL 语句中直接插入一个不转义的字符串。 比如 ORDER BY 子句

ORDER BY ${columnName}
3.2.2.5 sql 标签

SQL中可将重复的SQL提取出来,使用时用incloud引用即可。比如:

<?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.ltgx.mapper.UserMapper">

    <sql id="selectUser">
        select * from user
    </sql>

    <select id="selectUserById" parameterType="java.lang.Integer">
        <include refid="selectUser"></include> where id = #{id}
    </select>

    <select id="selectUserByName" parameterType="java.lang.String">
        <include refid="selectUser"></include> where username = #{username}
    </select>
    
</mapper>
3.2.2.6 动态sql

动态 SQL 是 MyBatis 的强大特性之一。

mybatis配置文件中,前门我们使用的sql都是比较简单的,有时候业务逻辑复杂时,我们的SQL是需要动态变化的,此时在前面介绍的就不能满足需求了。

3.2.2.6.1 if 标签
    <select id="selectUserByCondition" parameterType="com.ltgx.entity.User">
        select * from user
        <where>
            <if test="null != id">
                and id = #{id}
            </if>
            <if test="null != username">
                and username = #{username}
            </if>
        </where>
    </select>

我们根据实体类user的不同取值,使用不同的SQL语句进行查询。比如

id 不为空时可以根据id来查询

select * from user where id = ?

username不为空时根据username来查询

select * from user where username = ?

id和username都不为空时来查询

select * from user where id = ? and username = ?
3.2.2.6.2 foreach 标签

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)

<select id="selectPostIn" resultType="domain.blog.Post">
  select * from user
  <where>
     <foreach item="id" index="index" collection="ids"
      open="id in (" separator="," close=")">
        #{id}
    </foreach>
  </where>
</select>

最终sql:

select * from user where id in (?,?,?)

collection:代表要遍历的集合元素,主要编写是不要写#{}

open:代表语句的开始部分

close:代表语句的结束部分

item:代表遍历集合的每个元素,生成时的变量

separator:代表分隔符

3.2.2.6.3 choose、when、otherwise

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

<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>
3.2.2.6.4 trim、where、set

where

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG
  <where>
    <if test="state != null">
         AND 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>

set

用于动态更新语句的类似解决方案叫做 setset 元素可以用于动态包含需要更新的列,忽略其它不更新的列。

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

trim

自定义 trim 元素来定制 whereset 元素的功能,prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。

<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)

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

bind 元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文。

<select id="findByBind" resultMap="userResultMap" parameterType="com.ltgx.entity.User">
    <bind name="likeUsername" value="'%'+username+'%'"/>
    select id,username,password from user where username like #{likeUsername}
</select>

3.3 复杂映射开发

一对一和一对多使用 用户与订单 作为案例

用户表和订单表的关系:一个用户可以有多个订单,一个订单只能有一个用户

image-20200611140712362.png
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(225) DEFAULT NULL,
  `password` varchar(225) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;

INSERT INTO `demo`.`user` (`id`, `username`, `password`) VALUES ('1', '张三', '123456');
INSERT INTO `demo`.`user` (`id`, `username`, `password`) VALUES ('2', '李四', '456789');
INSERT INTO `demo`.`user` (`id`, `username`, `password`) VALUES ('3', '王五', '987654');

CREATE TABLE `orders` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `orderTime` datetime DEFAULT NULL,
  `money` decimal(10,2) DEFAULT NULL,
  `uid` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;
INSERT INTO `demo`.`orders` (`id`, `orderTime`, `money`, `uid`) VALUES ('1', '2020-06-10 14:10:59', '25.50', '1');
INSERT INTO `demo`.`orders` (`id`, `orderTime`, `money`, `uid`) VALUES ('2', '2020-06-10 14:11:08', '33.30', '1');
INSERT INTO `demo`.`orders` (`id`, `orderTime`, `money`, `uid`) VALUES ('3', '2020-06-04 14:11:22', '12.00', '2');
INSERT INTO `demo`.`orders` (`id`, `orderTime`, `money`, `uid`) VALUES ('4', '2020-06-02 14:11:34', '16.00', '3');

3.3.1 一对一查询

查询SQL:

select * from orders o,user u where o.uid=u.id; 

查询结果:

image-20200611141537989.png

编写用户实体:

@Data
public class User {
    private Integer id;
    private String username;
    private String password;
}

编写订单实体:

@Data
public class Orders {
    private Integer id;
    private LocalDateTime orderTime;
    private BigDecimal money;

    // 一个订单对应一个用户
    private User user;
}

编写订单Mapper:

public interface OrdersMapper {

    List<Orders> findAll();
}

编写OrdersMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ltgx.mapper.OrdersMapper">

    <resultMap id="orderResultMap" type="com.ltgx.entity.Orders">
        <id column="id" property="id"/>
        <result column="orderTime" property="orderTime"/>
        <result column="money" property="money"/>
        <result column="uid" property="user.id"/>
        <result column="username" property="user.username"/>
        <result column="password" property="user.password"/>
    </resultMap>

    <select id="findAll" resultMap="orderResultMap">
        select * from orders o,user u where o.uid=u.id
    </select>
</mapper>

<?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.ltgx.mapper.OrdersMapper">

    <resultMap id="orderResultMap" type="com.ltgx.entity.Orders">
        <id column="id" property="id"/>
        <result column="orderTime" property="orderTime"/>
        <result column="money" property="money"/>
        <association property="user" javaType="com.ltgx.entity.User">
            <id column="uid" property="id"/>
            <result column="username" property="username"/>
            <result column="password" property="password"/>
        </association>
    </resultMap>

    <select id="findAll" resultMap="orderResultMap">
        select * from orders o,user u where o.uid=u.id
    </select>
</mapper>

测试结果:

    @Test
    public void testMapperFindAllOrders() throws IOException {
        // 加载核心配置文件
        InputStream resourceAsStream =
                Resources.getResourceAsStream("SqlMapConfig.xml");
        // 获取SqlSession 工厂
        SqlSessionFactory sqlSessionFactory = new
                SqlSessionFactoryBuilder().build(resourceAsStream);
        // 获取SqlSession 连接
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 获取 Mapper
        OrdersMapper mapper = sqlSession.getMapper(OrdersMapper.class);
        // 查询所有数据
        List<Orders> orders = mapper.findAll();
        for (Orders order : orders) {
            System.out.println(order);
        }
        // 关闭连接
        sqlSession.close();
    }
image-20200611142739486.png

3.3.2 一对多查询

查询SQL:

select *,o.id oid from user u left join orders o on u.id=o.uid;

查询结果:

image-20200611143049477.png

编写用户实体:

@Data
public class User {
    private Integer id;
    private String username;
    private String password;

    // 一个用户有多个订单
    private List<Orders> orders;
}

编写订单实体:

@Data
public class Orders {
    private Integer id;
    private LocalDateTime orderTime;
    private BigDecimal money;

    // 一个订单对应一个用户
    private User user;
}

编写订单Mapper:

public interface UserMapper {

    List<User> findAll();

}

编写UserMapper.xml

<mapper namespace="com.ltgx.mapper.UserMapper">

    <resultMap id="userResultMap" type="com.ltgx.entity.User">
        <id column="uid" property="id"/>
        <result column="username" property="username"/>
        <result column="password" property="password"/>
        <collection property="orders" ofType="com.ltgx.entity.Orders">
            <id column="oid" property="id"/>
            <result column="orderTime" property="orderTime"/>
            <result column="money" property="money"/>
        </collection>
    </resultMap>

    <select id="findAll" resultMap="userResultMap">
        select *,o.id oid from user u left join orders o on u.id=o.uid
    </select>

</mapper>

测试结果:

    @Test
    public void testMapperFindAll() throws IOException {
        // 加载核心配置文件
        InputStream resourceAsStream =
                Resources.getResourceAsStream("SqlMapConfig.xml");
        // 获取SqlSession 工厂
        SqlSessionFactory sqlSessionFactory = new
                SqlSessionFactoryBuilder().build(resourceAsStream);
        // 获取SqlSession 连接
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 获取 Mapper
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        // 查询所有数据
        List<User> users = mapper.findAll();
        for (User user : users) {
            System.out.println(user.getUsername());
            for (Orders order : user.getOrders()) {
                System.out.println(order);
            }
            System.out.println("--------------------人工分割线-----------------");
        }
        // 关闭连接
        sqlSession.close();
    }
image-20200611144114240.png

3.3.3 多对多查询

多对多使用 用户与角色 作为案例

用户和角色关系:一个用户可以有多个角色,一个角色可以被多个人使用

image-20200611145143812.png
CREATE TABLE `role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `rolename` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;

CREATE TABLE `user_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL,
  `role_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4;

INSERT INTO `demo`.`role` (`id`, `rolename`) VALUES ('1', 'CTO');
INSERT INTO `demo`.`role` (`id`, `rolename`) VALUES ('2', 'CEO');
INSERT INTO `demo`.`role` (`id`, `rolename`) VALUES ('3', 'CXO');

INSERT INTO `demo`.`user_role` (`id`, `user_id`, `role_id`) VALUES ('1', '1', '1');
INSERT INTO `demo`.`user_role` (`id`, `user_id`, `role_id`) VALUES ('2', '1', '2');
INSERT INTO `demo`.`user_role` (`id`, `user_id`, `role_id`) VALUES ('3', '1', '3');
INSERT INTO `demo`.`user_role` (`id`, `user_id`, `role_id`) VALUES ('4', '2', '3');
INSERT INTO `demo`.`user_role` (`id`, `user_id`, `role_id`) VALUES ('5', '3', '2');

查询SQL语句:

SELECT
    u.*, r.*, r.id rid
FROM
    USER u
LEFT JOIN user_role ur ON u.id = ur.user_id
INNER JOIN role r ON ur.role_id = r.id;

创建Role实体类

@Data
public class Role {
    private Integer id;
    private String rolename;
}

User实体类

@Data
public class User {
    private Integer id;
    private String username;
    private String password;

    // 一个用户有多个订单
    private List<Orders> orders;
    // 当前用户对应的角色
    private List<Role> roles;
}

编写UserMapper接口

public interface UserMapper {

    List<User> findAllUserRole();
}

编写UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ltgx.mapper.UserMapper">

    <resultMap id="userResultMap" type="com.ltgx.entity.User">
        <result column="uid" property="id"/>
        <result column="username" property="username"/>
        <result column="password" property="password"/>
        <collection property="orders" ofType="com.ltgx.entity.Orders">
            <id column="oid" property="id"/>
            <result column="orderTime" property="orderTime"/>
            <result column="money" property="money"/>
        </collection>
        <collection property="roles" ofType="com.ltgx.entity.Role">
            <result column="rid" property="id"/>
            <result column="rolename" property="rolename"/>
        </collection>
    </resultMap>

    <select id="findAllUserRole" resultMap="userResultMap">
        SELECT
            u.*, r.*, r.id rid
        FROM
            user u
        LEFT JOIN user_role ur ON u.id = ur.user_id
        INNER JOIN role r ON ur.role_id = r.id
    </select>
</mapper>

测试结果

    @Test
    public void testMapperFindAllUserRole() throws IOException {
        // 加载核心配置文件
        InputStream resourceAsStream =
                Resources.getResourceAsStream("SqlMapConfig.xml");
        // 获取SqlSession 工厂
        SqlSessionFactory sqlSessionFactory = new
                SqlSessionFactoryBuilder().build(resourceAsStream);
        // 获取SqlSession 连接
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 获取 Mapper
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        // 查询所有数据
        List<User> users = mapper.findAllUserRole();
        for (User user : users) {
            System.out.println(user.getUsername());
            for (Role role : user.getRoles()) {
                System.out.println(role);
            }
            System.out.println("--------------------人工分割线-----------------");
        }
        // 关闭连接
        sqlSession.close();
    }
image-20200611150734952.png

3.4 注解开发

随着注解越来越流行,Mybatis也可以使用注解开发方式,这样我们就可以减少编写Mapper映射文件。

3.4.1 常用注解

注解 说明
@Select 实现查询
@Insert 实现新增
@Update 实现修改
@Delete 实现删除
@Result 实现结果集封装
@Results 可以与@Result结合使用,封装多个结果集
@One 实现一对一结果集封装
@Many 实现一对多结果集封装
3.4.1.1 @Results

@Results:代替的是标签<resultMap>,该注解中可以使用单个@Result注解,也可以使用@Result集合,使用格式:

@Results({
    @Result(),
    @Result()
})
@Results(@Result())

@Result:代替了<id>、<result>标签,其@Result注解的属性:

column:数据库中的列表

property:需要装配的属性名

one:需要使用的@One注解,@Results({@Result(one = @One())})

many:需要使用的@Many注解,@Results({@Result(many = @Many())})

3.4.1.2 @One
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface One {
    String select() default "";

    FetchType fetchType() default FetchType.DEFAULT;
}

select为关联需要查询SQL的全局唯一标识ID【namespace.id】

3.4.1.3 @Many
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Many {
    String select() default "";

    FetchType fetchType() default FetchType.DEFAULT;
}

select为关联需要查询SQL的全局唯一标识ID【namespace.id】

3.4.2 一对一

编写实体对象

@Data
public class Orders {
    private Integer id;
    private LocalDateTime orderTime;
    private BigDecimal money;

    // 一个订单对应一个用户
    private User user;
}

@Data
public class User {
    private Integer id;
    private String username;
    private String password;
}

编写mapper接口

public interface OrdersAnnotationMapper {

    @Select("select * from orders")
    @Results({
            @Result(column = "id", property = "id"),
            @Result(column = "orderTime", property = "orderTime"),
            @Result(column = "money", property = "money"),
            @Result(column = "uid", property = "user", javaType = User.class, one = @One(select = "com.ltgx.mapper.UserAnnotationMapper.selectUserById"))
    })
    List<Orders> selectOrders();
}

public interface UserAnnotationMapper {

    @Select("select * from user where id = #{id}")
    User selectUserById(Integer id);

}

测试代码:

    @Test
    public void testOneToOne() throws IOException {
        // 加载核心配置文件
        InputStream resourceAsStream =
                Resources.getResourceAsStream("SqlMapConfig.xml");
        // 获取SqlSession 工厂
        SqlSessionFactory sqlSessionFactory = new
                SqlSessionFactoryBuilder().build(resourceAsStream);
        // 获取SqlSession 连接
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 获取 Mapper
        OrdersAnnotationMapper mapper = sqlSession.getMapper(OrdersAnnotationMapper.class);
        // 查询所有数据
        List<Orders> ordersList = mapper.selectOrders();
        for (Orders orders : ordersList) {
            System.out.println(orders);
        }
        // 关闭连接
        sqlSession.close();
    }
image-20200611155043694.png

3.4.2 一对多

编写实体对象

@Data
public class Orders {
    private Integer id;
    private LocalDateTime orderTime;
    private BigDecimal money;

    // 一个订单对应一个用户
    private User user;
}

@Data
public class User {
    private Integer id;
    private String username;
    private String password;
    
    // 一个用户有多个订单
    private List<Orders> orders;
}

编写mapper接口

public interface UserAnnotationMapper {

    @Select("select * from user")
    @Results({
            @Result(column = "id", property = "id"),
            @Result(column = "username", property = "username"),
            @Result(column = "password", property = "password"),
            @Result(property = "orders", column = "id", javaType = List.class, many = @Many(select = "com.ltgx.mapper.OrdersAnnotationMapper.selectOrdersByUserId"))
    })
    List<User> selectUsers();
}

public interface OrdersAnnotationMapper {

    @Select("select * from orders where uid = #{userId}")
    List<Orders> selectOrdersByUserId(Integer userId);

}

测试代码:

    @Test
    public void selectUsers() throws IOException {
        // 加载核心配置文件
        InputStream resourceAsStream =
                Resources.getResourceAsStream("SqlMapConfig.xml");
        // 获取SqlSession 工厂
        SqlSessionFactory sqlSessionFactory = new
                SqlSessionFactoryBuilder().build(resourceAsStream);
        // 获取SqlSession 连接
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 获取 Mapper
        UserAnnotationMapper mapper = sqlSession.getMapper(UserAnnotationMapper.class);
        // 查询所有数据
        List<User> userList = mapper.selectUsers();
        for (User user : userList) {
            System.out.println(user.getUsername());
            for (Orders order : user.getOrders()) {
                System.out.println(order);
            }
            System.out.println("--------------------优雅的分隔符--------------------");
        }
        // 关闭连接
        sqlSession.close();
    }
image-20200611160827966.png

3.4.2 多对多

编写实体对象

@Data
public class Orders {
    private Integer id;
    private LocalDateTime orderTime;
    private BigDecimal money;

    // 一个订单对应一个用户
    private User user;
}

@Data
public class User {
    private Integer id;
    private String username;
    private String password;
    
    // 当前用户对应的角色
    private List<Role> roles;
}

编写mapper接口

public interface UserAnnotationMapper {

    @Select("select * from user")
    @Results({
            @Result(column = "id", property = "id"),
            @Result(column = "username", property = "username"),
            @Result(column = "password", property = "password"),
            @Result(property = "roles", column = "id", javaType = List.class, many = @Many(select = "com.ltgx.mapper.RoleMapper.selectRolesByUserId"))
    })
    List<User> selectUsersAndRoles();
}

public interface RoleMapper {

    @Select("select r.id,r.rolename from role r,user_role ur where r.id=ur.role_id and ur.user_id=#{userId}")
    List<Role> selectRolesByUserId(Integer userId);

}

测试代码:

    @Test
    public void selectUsersAndRoles() throws IOException {
        // 加载核心配置文件
        InputStream resourceAsStream =
                Resources.getResourceAsStream("SqlMapConfig.xml");
        // 获取SqlSession 工厂
        SqlSessionFactory sqlSessionFactory = new
                SqlSessionFactoryBuilder().build(resourceAsStream);
        // 获取SqlSession 连接
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 获取 Mapper
        UserAnnotationMapper mapper = sqlSession.getMapper(UserAnnotationMapper.class);
        // 查询所有数据
        List<User> userList = mapper.selectUsersAndRoles();
        for (User user : userList) {
            System.out.println(user.getUsername());
            for (Role role : user.getRoles()) {
                System.out.println(role);
            }
            System.out.println("--------------------优雅的分隔符--------------------");
        }
        // 关闭连接
        sqlSession.close();
    }
image-20200611161812056.png

4. Mybatis 拓展

4.1 Mybatis 插件

4.1.1 插件简介

    一般情况下,开源框架都还提供插件或其他形式的拓展点,供开发者自行拓展。这样的好处是显而易见的,一是增加了框架的灵活性,二是开发者可以结合实际需求,对框架进行拓展,使其更好的工作。

    以Mybatis为例,我们可以基于Mybatis插件机制实现分页、分表、监控等。由于业务和插件无关,业务也无法感知插件的存在。因此可以无感织入插件,在无形中增强功能。

4.1.2 Mybatis 插件介绍

    Mybatis是一个应用广泛的优秀ORM开源框架,这个框架具有强大的灵活性,在四大组件(Executor、StatementHandler、ParameterHandler、ResultSetHandler)出提供了简单易用的插件扩展机制。

    Mybatis对持久层的操作就是借助与四大核心对象。Mybatis支持用插件对四大核心组件进行拦截,对Mybatis来说插件就是拦截器,用来增强核心对象的功能,增强功能本质上是借助与底层的动态代理实现的,换句话说,Mybatis中的四大对象都是代理对象
image-20200611163546833.png

MyBatis 所允许拦截的方法如下:

  • 执行器(Executor):update,query,commit,rollback等

  • SQL语法构建器(StatementHandler):prepare,batch,update,query等

  • 参数处理器(ParameterHandler):getParameterObject,setParameters

  • 结果集处理器(ResultSetHandler):handleResultSets,handleCursorResultSets,handleOutputParameters

4.1.3 Mybatis 插件原理

四大对象创建后流程解析

1)每个创建出来的对象都不是直接返回的, 而是由interceptorChain.plugin(xxxHandler)生成的代理对象

2)InterceptorChain中获取到所有的interceptor(拦截器,插件需要实现的接口), 再调用interceptor.plugin(target),返回target包装后的对象

3)插件机制,我们可以使用插件为目标对象创建一个代理对象;类似AOP,我们的插件可以为四大组件创建出代理对象,代理对象就可以拦截到四大对象的每一个执行。

以 Executor 为例

public class DefaultSqlSessionFactory implements SqlSessionFactory {
......
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 创建执行器
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
    .......
}
public class Configuration {

......
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    // 根据执行器类型构建执行器
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    // 构建执行器后通过 interceptorChain 生成代理对象
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
......
}
public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<>();

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      // 获取所有拦截器,创建代理类
      target = interceptor.plugin(target);
    }
    return target;
  }

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}
public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  default void setProperties(Properties properties) {
    // NOP
  }

}
public class Plugin implements InvocationHandler {
......
  // 创建代理对象
  public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        // 获取被拦截器拦截的方法列表
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
        // 检测方法列表是否包含在拦截方法
      if (methods != null && methods.contains(method)) {
          // 执行插件逻辑
        return interceptor.intercept(new Invocation(target, method, args));
      }
        // 执行被拦截的原方法
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }
......
}

4.1.4 自定义插件

1)插件类编码

/**
 * @author jie.luo
 * @description SQL执行之间统计
 * @since 2020-06-11
 */
@Intercepts({
        @Signature(
                type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
        )
})
public class SqlExecutionTimeInterceptor implements Interceptor {

    /**
     * 拦截方法:只要被拦截的目标对象的方法被执行时,每次都会执行该方法
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        System.out.println("开始时间 : " + LocalDateTime.now());
        // 让原方法执行
        Object proceed = invocation.proceed();
        System.out.println("结束时间 : " + LocalDateTime.now());
        return proceed;
    }

    /**
     * 把当前的拦截器,生成代理,存到拦截器链中
     *
     * @param target 被拦截的目标对象
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    /**
     * 获取配置文件的参数
     */
    @Override
    public void setProperties(Properties properties) {
        System.out.println("参数 " + properties);
    }
}

2)SQLMapConfig.xml配置插件

<plugins>
    <plugin interceptor="com.ltgx.interceptor.SqlExecutionTimeInterceptor">
        <property name="param" value="参数"/>
    </plugin>
</plugins>

3)测试代码

@Test
public void selectOrders() throws IOException {
    // 加载核心配置文件
    InputStream resourceAsStream =
    Resources.getResourceAsStream("SqlMapConfig.xml");
    // 获取SqlSession 工厂
    SqlSessionFactory sqlSessionFactory = new
    SqlSessionFactoryBuilder().build(resourceAsStream);
    // 获取SqlSession 连接
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 获取 Mapper
    OrdersAnnotationMapper mapper = sqlSession.getMapper(OrdersAnnotationMapper.class);
    // 查询所有数据
    List<Orders> ordersList = mapper.selectOrders();
    for (Orders orders : ordersList) {
    System.out.println(orders);
    }
    // 关闭连接
    sqlSession.close();
}
image-20200611174507476.png

4.2 分页插件

MyBatis 可以使用第三方的插件来对功能进行扩展,分页组手 pagehelper 是将分页的复杂操作进行封装,

使用简单的方式既可以获得分页相关的相关数据。

1)添加依赖

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.11</version>
</dependency>

2)核心配置文件添加分页插件

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>

3)测试代码

    private UserAnnotationMapper userAnnotationMapper;

    @Before
    public void testBefore() throws IOException {
        // 加载核心配置文件
        InputStream resourceAsStream =
                Resources.getResourceAsStream("SqlMapConfig.xml");
        // 获取SqlSession 工厂
        SqlSessionFactory sqlSessionFactory = new
                SqlSessionFactoryBuilder().build(resourceAsStream);
        // 获取SqlSession 连接
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 获取 Mapper
        userAnnotationMapper = sqlSession.getMapper(UserAnnotationMapper.class);

    }

    @Test
    public void page() {

        PageHelper.startPage(1, 1);
        // 查询所有数据
        List<User> userList = userAnnotationMapper.selectUsers();
        PageInfo<User> pageInfo = new PageInfo<>(userList);
        
        // 分页相关参数
        System.out.println("总条数 " + pageInfo.getTotal());
        System.out.println("总页数 " + pageInfo.getPages());
        System.out.println("页码 " + pageInfo.getPageNum());
        System.out.println("页大小 " + pageInfo.getPageSize());
        System.out.println("是否为首页 " + pageInfo.isIsFirstPage());
        System.out.println("是否为末页 " + pageInfo.isIsLastPage());

        System.out.println(pageInfo);

    }

image-20200611180657128.png

4.3 通用mapper

通用Mapper就是为了解决单表增删改查,基于Mybatis插件机制。开发时不需要编写SQL,不需要在DAO增加方法,只要写好实体类,就能支持相应的增删改查方法。

通用Mapper Github地址:https://github.com/abel533/Mapper

1)导入依赖

<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper</artifactId>
    <version>4.1.5</version>
</dependency>

2)测试代码

public class TkMapperTest {

    private RoleMapper roleMapper;

    @Before
    public void testBefore() throws IOException {
        // 加载核心配置文件
        InputStream resourceAsStream =
                Resources.getResourceAsStream("SqlMapConfig.xml");
        // 获取SqlSession 工厂
        SqlSessionFactory sqlSessionFactory = new
                SqlSessionFactoryBuilder().build(resourceAsStream);
        // 获取SqlSession 连接
        SqlSession sqlSession = sqlSessionFactory.openSession();

        MapperHelper mapperHelper = new MapperHelper();
        mapperHelper.processConfiguration(sqlSession.getConfiguration());

        // 获取 Mapper
        roleMapper = sqlSession.getMapper(RoleMapper.class);
    }

    @Test
    public void test() {

        // 查询所有
        List<Role> roles = roleMapper.selectAll();
        System.out.println(roles);

        // Example 用法
        Example example = new Example(Role.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andLike("rolename", "%C%");
        example.orderBy("id").desc();
        List<Role> roles1 = roleMapper.selectByExample(example);
        System.out.println(roles1);

        // Example 用法,设置查询列
        Example example1 = new Example(Role.class);
        example1.selectProperties("rolename");
        Example.Criteria criteria1 = example1.createCriteria();
        criteria1.andLike("rolename", "%C%");
        List<Role> roles2 = roleMapper.selectByExample(example1);
        System.out.println(roles2);

    }

}

SQL

==>  Preparing: SELECT id,rolename FROM role 
==> Parameters: 
<==    Columns: id, rolename
<==      Total: 3
[Role(id=1, rolename=CTO), Role(id=2, rolename=CEO), Role(id=3, rolename=CXO)]

==>  Preparing: SELECT id,rolename FROM role WHERE ( ( rolename like ? ) ) order by id DESC 
==> Parameters: %C%(String)
<==    Columns: id, rolename
<==      Total: 3
[Role(id=3, rolename=CXO), Role(id=2, rolename=CEO), Role(id=1, rolename=CTO)]

==>  Preparing: SELECT rolename FROM role WHERE ( ( rolename like ? ) ) 
==> Parameters: %C%(String)
<==    Columns: rolename
<==      Total: 3
[Role(id=null, rolename=CTO), Role(id=null, rolename=CEO), Role(id=null, rolename=CXO)]

4.4 Mybatis Plus

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

官网地址:https://mp.baomidou.com

image-20200615125853362.png

5. Spring Boot 整合

5.1 Spring Boot 整合 Mybatis

Spring Boot 整个 Mybatis、PageHelper分页插件

项目整体目录结构

image-20200615113509111.png

5.1.1 添加依赖

<parent>
    <artifactId>spring-boot-starter-parent</artifactId>
    <groupId>org.springframework.boot</groupId>
    <version>2.1.14.RELEASE</version>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>

    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.1</version>
    </dependency>

    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper-spring-boot-starter</artifactId>
        <version>1.2.12</version>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.21</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.49</version>
    </dependency>

    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

5.1.2 配置文件信息

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://luojie.site:33306/demo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 123456
    druid:
      #初始化时建立物理连接的个数
      initial-size: 10
      #最小连接池数量
      min-idle: 10
      #最大连接池数量
      max-active: 100
      #获取连接时最大等待时间
      max-wait: 60000
      stat-view-servlet:
        #配置监控页面访问登录名称
        login-username: admin
        #配置监控页面访问密码
        login-password: admin
      filter:
        stat:
          #是否开启慢sql查询监控
          log-slow-sql: true
          #慢SQL执行时间
          slow-sql-millis: 2

mybatis:
  # 配置XML配置文件中实例类别名路径
  type-aliases-package: org.example.mybatis.demo.domain
  configuration:
    # 开启驼峰命名规则
    map-underscore-to-camel-case: true
  # 配置MyBatis的xml配置文件路径
  mapper-locations: classpath:mapper/*Mapper.xml
logging:
  level:
    org.example: debug

5.1.3 映射文件信息

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.example.mapper.UserMapper">

    <select id="selectAll" resultType="org.example.entity.User">
        select * from user
    </select>

</mapper>

5.1.4 相关代码

POJO 对象

@Data
public class User {
    private Integer id;
    private String username;
    private String password;
}

UserMapper

@Mapper
public interface UserMapper {

    List<User> selectAll();

}

UserService

public interface UserService {

    List<User> findUserAll();

    PageInfo<User> page(int page, int size);
}

UserServiceImpl

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public List<User> findUserAll() {
        return userMapper.selectAll();
    }

    @Override
    public PageInfo<User> page(int page, int size) {
        PageHelper.startPage(page, size);
        List<User> users = userMapper.selectAll();
        PageInfo<User> pageInfo = new PageInfo<>(users);
        return pageInfo;
    }

}

5.1.5 测试代码

@RunWith(SpringRunner.class)
@SpringBootTest(classes = MybatisApplication.class)
public class MybatisTest {

    @Autowired
    private UserService userService;

    @Test
    public void test() {
        List<User> userAll = userService.findUserAll();
        System.out.println(userAll);
    }

    @Test
    public void page() {
        PageInfo<User> page = userService.page(1,2);
        System.out.println(page);
    }

}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容