乐趣区

关于java:三MyBatis从入门到入土使用详解

这是 mybatis 系列第 3 篇。没看前文的倡议先去【Java 冢狐】公众号中查看前文,不便了解和把握

MyBatis 应用详解

上篇咱们手动开发了一个 MyBatis 我的项目,然而咱们仅仅是编写了代码,对于整个我的项目是如何运行以及每个代码的意义都没有认真的剖析和阐明,那么接下来咱们就开始剖析每个代码的意义以及如何编写这个代码

配置 MyBatis 全局配置文件

要应用 Mybatis 来操作数据库,那么当然就须要配置数据库相干信息,这件须要在 mybatis 全局配置文件中进行。即全局配置的 xml 文件,其对整个 MyBatis 进行事务的反对、数据库的配置等信息的配置。咱们个别放在 main/resource 文件中,如下所示

<configuration>
    <!-- 环境配置,能够配置多个环境 -->
    <environments default="chat01">
        <!--
            environment 用来对某个环境进行配置
            id:环境标识,惟一
         -->
        <environment id="chat01">
            <!-- 事务管理器工厂配置 -->
            <transactionManager type="org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory"/>
            <!-- 数据源工厂配置,应用工厂来创立数据源 -->
            <dataSource type="org.apache.ibatis.datasource.pooled.PooledDataSourceFactory">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatisdemo?characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

configuration 元素

这个是 mybatis 全局配置文件的根元素,每个配置文件只有一个

environments 元素

用来配置 mybatis 的环境信息,用来配置多个环境的,具体的一个环境应用 environment 元素进行配置,environment 元素有个 id 用来标识某个具体的环境。

environments 元素有个 default 属性,用来指定默认应用哪个环境,如下面默认应用的是 chat01。

environment 元素

用来配置具体的环境信息,这个元素上面有两个子元素:transactionManager 和 dataSource

  • transactionManager 元素

用来配置事务工厂的,有个 type 属性,type 的值必须是 org.apache.ibatis.transaction.TransactionFactory 接口的实现类,用来创立事务管理器对象的,TransactionFactory 接口默认有 2 个实现:

org.apache.ibatis.transaction.managed.ManagedTransactionFactory
org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory

个别状况下咱们应用 org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory 这个,mybatis 和其余框架集成,比方和 spring 集成,事务交由 spring 去管制。

  • dataSource 元素

这个用来配置数据源的,type 属性的值必须为接口 org.apache.ibatis.datasource.DataSourceFactory 的实现类,DataSourceFactory 也是一个工厂,用来创立数据源 javax.sql.DataSource 对象的,mybatis 中这个接口默认有 3 个实现类:

org.apache.ibatis.datasource.jndi.JndiDataSourceFactory
org.apache.ibatis.datasource.pooled.PooledDataSourceFactory
org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory

咱们应用第 2 个 org.apache.ibatis.datasource.pooled.PooledDataSourceFactory,这个用来创立一个数据库连接池类型的数据源,能够实现数据库连贯共用,缩小连贯反复创立销毁的工夫。
配置数据源须要指定数据库连贯的属性信息,比方:驱动、连贯 db 的 url、用户名、明码,这个在 dataSource 元素上面的 property 中配置,property 元素的格局:

<property name="属性名称" value="值"/>

创立 Mapper xml 文件

在 mybatis 中个别咱们将一个表的所有 sql 操作写在一个 mapper xml 中,个别命名为 XXXMapper.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="zhonghu.mybatis.chat01.UserMapper">
</mapper>

mapper xml 根元素为 mapper,这个元素有个 namespace 属性,零碎中会有很多表,每个表对应一个 Mapper xml,为了避免 mapper 文件反复,咱们须要给每个 mapper xml 文件须要指定一个 namespace,通过这个能够辨别每个 mapper xml 文件,下面咱们指定为 zhonghu.mybatis.chat01.UserMapper。

一会对 user 表的所有操作相干的 sql,咱们都会写在下面这个 xml 中。

mybatis 全局配置文件中引入 Mapper xml 文件

user.xml 咱们写好了,如何让 mybatis 晓得这个文件呢,此时咱们须要在 mybatis-config.xml 全局配置文件中引入 UserMapper.xml,在 mybatis-config.xml 退出上面配置:

<mappers>
        <mapper resource="mapper/user.xml"/>
    </mappers>

mappers 元素上面有多个 mapper 元素,通过 mapper 元素的 resource 属性能够引入 Mapper xml 文件,resource 是绝对于 classes 的门路。

下面说的都是一些配置文件,配置文件都 ok 了,上面咱们就须要将 mybatis 跑起来了,此时须要应用到 mybatis 中的一些 java 对象了。

构建 SqlSessionFactory 对象

// 指定 mybatis 全局配置文件
String resource = "mybatis-config.xml";
// 读取全局配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
// 构建 SqlSessionFactory 对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

SqlSessionFactory 是一个接口,是一个重量级的对象,SqlSessionFactoryBuilder 通过读取全局配置文件来创立一个 SqlSessionFactory,创立这个对象是比拟耗时的,次要耗时在对 mybatis 全局配置文件的解析下面,全局配置文件中蕴含很多内容,SqlSessionFactoryBuilder 通过解析这些内容,创立了一个简单的 SqlSessionFactory 对象,这个对象的生命周期个别和利用的生命周期是一样的,随着利用的启动而创立,随着利用的进行而完结,所以个别是一个全局对象,个别状况下一个 db 对应一个 SqlSessionFactory 对象。

构建 SqlSession 对象

SqlSession 相当于 jdbc 中的 Connection 对象,相当于数据库的一个连贯,能够用 SqlSession 来对 db 进行操作:如执行 sql、提交事务、敞开连贯等等,须要通过 SqlSessionFactory 来创立 SqlSession 对象,SqlSessionFactory 中罕用的有 2 个办法来创立 SqlSession 对象,如下:

// 创立一个 SqlSession,默认不会主动提交事务
SqlSession openSession();
// 创立一个 SqlSession,autoCommit:指定是否主动提交事务
SqlSession openSession(boolean autoCommit);

SqlSession 接口中很多办法,间接用来操作 db,办法清单如下,大家眼生一下:

<T> T selectOne(String statement);
<T> T selectOne(String statement, Object parameter);
<E> List<E> selectList(String statement);
<E> List<E> selectList(String statement, Object parameter);
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
<K, V> Map<K, V> selectMap(String statement, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
<T> Cursor<T> selectCursor(String statement);
<T> Cursor<T> selectCursor(String statement, Object parameter);
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
void select(String statement, Object parameter, ResultHandler handler);
void select(String statement, ResultHandler handler);
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
int insert(String statement);
int insert(String statement, Object parameter);
int update(String statement);
int update(String statement, Object parameter);
int delete(String statement);
int delete(String statement, Object parameter);
void commit();
void commit(boolean force);
void rollback();
void rollback(boolean force);
List<BatchResult> flushStatements();
void close();
void clearCache();
Configuration getConfiguration();
<T> T getMapper(Class<T> type);
Connection getConnection();

下面以 select 结尾的能够对 db 进行查问操作,insert 相干的能够对 db 进行插入操作,update 相干的能够对 db 进行更新操作。

引入 lombok 反对(非必须)

Lombok 能以简略的注解模式来简化 java 代码,进步开发人员的开发效率。例如开发中常常须要写的 javabean,都须要花工夫去增加相应的 getter/setter,兴许还要去写结构器、equals 等办法,而且须要保护,当属性多时会呈现大量的 getter/setter 办法,这些显得很简短也没有太多技术含量,一旦批改属性,就容易呈现遗记批改对应办法的失误。

Lombok 能通过注解的形式,在编译时主动为属性生成结构器、getter/setter、equals、hashcode、toString 办法。呈现的神奇就是在源码中没有 getter 和 setter 办法,然而在编译生成的字节码文件中有 getter 和 setter 办法。这样就省去了手动重建这些代码的麻烦,使代码看起来更简洁些。

lombok 的应用步骤

  • 先在 idea 中装置 lombok 插件
    关上 idea,点击 File->Settings->plugins,而后搜寻 Lombok Plugin,点击装置就能够了。
  • maven 中引入 lombok 反对
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <version>1.18.10</version>
   <scope>provided</scope>
</dependency>
  • 代码中应用 lombok 相干性能

引入 logback(非必须)

为了不便查看 mybatis 运行过程中产生的日志,比方:执行的 sql、sql 的参数、sql 的执行后果等等调试信息,咱们须要引入日志框架的反对,logback 是一个很好的日志框架,此处咱们就应用这个

mybatis 中集成 logback 步骤

  • maven 中引入 logback 反对
<dependency>
   <groupId>ch.qos.logback</groupId>
   <artifactId>logback-classic</artifactId>
   <version>1.2.3</version>
</dependency>
  • src/main/resources 中创立 logback.xml 文件:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
   <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
       <encoder>
           <pattern>%d{mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
       </encoder>
   </appender>
   <logger name="zhonghu" level="debug" additivity="false">
       <appender-ref ref="STDOUT" />
   </logger>
</configuration>

logback.xml 具体的写法不是本文探讨的范畴,有趣味的敌人能够去钻研一下 logback 具体的用法。

写一个测试用例

下面说了这么多,上面咱们就写一个测试类来演示一下

内容如下:

package zhonghu.mybatis.chat01;

import lombok.extern.slf4j.Slf4j;
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.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;

@Slf4j
public class UserMapperTest {
    private SqlSessionFactory sqlSessionFactory;
    @Before
    public void before() throws IOException {
        // 指定 mybatis 全局配置文件
        String resource = "mybatis-config.xml";
        // 读取全局配置文件
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 构建 SqlSessionFactory 对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        this.sqlSessionFactory = sqlSessionFactory;
    }
    @Test
    public void sqlSession() {SqlSession sqlSession = this.sqlSessionFactory.openSession();
        log.info("{}", sqlSession);
    }

}

下面代码中有个 @Slf4j 注解,这个是 lombok 提供的,能够在这个类中生成上面代码:

private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(UserTest.class);

运行一下下面的用例:sqlSession 办法,输入如下:

37:52.473 [main] INFO  z.mybatis.chat01.UserMapperTest - org.apache.ibatis.session.defaults.DefaultSqlSession@2d127a61

至此咱们就搭建了一个最小化的 Mybatis 我的项目,前面就须要咱们依据须要编写咱们的 sql 文件

应用 SqlSesion 执行 sql 操作

SqlSession 常见的用法

SqlSession 相当于一个连贯,能够应用这个对象对 db 执行增删改查操作,操作结束之后须要敞开,应用步骤:

  • 获取 SqlSession 对象:通过该 sqlSessionFactory.openSession 办法获取 SqlSession 对象
  • 对 db 进行操作:应用 SqlSession 对象进行 db 操作
  • 敞开 SqlSession 对象:sqlSession.close();

如下所示

// 获取 SqlSession
SqlSession sqlSession = this.sqlSessionFactory.openSession();
try {// 执行业务操作,如:增删改查} finally {
    // 敞开 SqlSession
    sqlSession.close();}

下面咱们将 SqlSession 的敞开放在 finally 块中,确保 close()肯定会执行。

新增操作

需要:传入 UserModel 对象,而后将这个对象的数据插入到 user 表中。

创立一个 UserModel

UserModel 类,代码如下:

package zhonghu.mybatis.chat01;

import lombok.*;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class UserModel {
    private Long id;
    private String name;
    private Integer age;
    private Double salary;
}

这个类的字段和 user 表对应。

user.xml 中定义插入操作

咱们说过了,对 user 表的所有 sql 操作,咱们都放在 user.xml 中,咱们在 user.xml 中退出上面配置,应用 insert 元素定义插入操作:

<!-- insert 用来定义一个插入操作
     id:操作的具体标识
     parameterType:指定插入操作承受的参数类型
 -->
<insert id="insertUser" parameterType="zhonghu.mybatis.chat01.UserModel">
    <![CDATA[INSERT INTO user (id,name,age,salary) VALUES (#{id},#{name},#{age},#{salary})
     ]]>
</insert>
  • insert 元素用来定义了一个对 db 的 insert 操作
  • id:是这个操作的一个标识,一会通过 mybatis 执行操作的时候会通过这个 namespace 和 id 援用到这个 insert 操作,
  • parameterType:用来指定这个 insert 操作承受的参数的类型,能够是:各种 javabean、map、list、collection 类型的 java 对象,咱们这个插入承受的是 UserModel 对象。
  • insert 元素外部定义了具体的 sql,能够看到是一个 insert 的 sql,向 user 表插入数据。

须要插入的值从 UserModel 对象中获取,取 UserModel 对象的的字段,应用 #{字段}这种格局能够获取到 UserModel 中字段的值。

调用 SqlSession.insert 办法执行插入操作

user 插入的 sql 咱们曾经在 UserMapper 中写好,此时咱们怎么调用呢?

须要调用 SqlSession.insert 办法:

int insert(String statement, Object parameter)

这个办法有 2 个参数:

  • statement:示意那个操作,值为 Mapper xml 的 namespace. 具体操作的 id,如须要调用 UserMapper.xml 中的 insertUser 操作,这个值就是:
zhonghu.mybatis.chat01.UserMapper.insertUser
  • parameter:insert 操作的参数,和 Mapper xml 中的 insert 中的 parameterType 指定的类型统一。

返回值为插入的行数。

UserTest 类中新增一个测试用例:

  @Test
    public void insertUser() {try (SqlSession sqlSession = this.sqlSessionFactory.openSession(false);) {
            // 创立 UserModel 对象
            UserModel userModel = UserModel.builder().id(69L).name("Java 冢狐").age(30).salary(50000D).build();
            // 执行插入操作
            int result = sqlSession.insert("zhonghu.mybatis.chat01.UserMapper.insertUser", userModel);
            log.info("插入影响行数:{}", result);
            // 提交事务
            sqlSession.commit();}
    }

运行输入如下:

05:15.831 [main] DEBUG z.m.chat01.UserMapper.insertUser - ==>  Preparing: INSERT INTO user (id,name,age,salary) VALUES (?,?,?,?) 
05:15.853 [main] DEBUG z.m.chat01.UserMapper.insertUser - ==> Parameters: 69(Long), Java 冢狐(String), 30(Integer), 50000.0(Double)
05:15.951 [main] DEBUG z.m.chat01.UserMapper.insertUser - <==    Updates: 1
05:15.952 [main] INFO  z.mybatis.chat01.UserMapperTest - 插入影响行数:1

输入中打印了具体的 sql 语句,以及 sql 的参数信息,能够看到 Mapper xml 中的 #{}被替换为了?,这个应用到了 jdbc 中的 PreparedStatement 来对参数设置值。

输入中的第二行具体列出了参数的值以及每个值的类型。

第三行输入了 insert 的后果为 1,示意插入胜利了 1 行记录。

下面代码中创立 SqlSession,咱们应用的是 sqlSessionFactory.openSession()创立的,这个办法创立的 SqlSession,内部事务是非主动提交的形式,所以须要咱们手动提交:

增、删、改操作都须要提交事务

sqlSession.commit();

如果想主动提交事务,在下面在创立 SqlSession 的时候改为 sqlSessionFactory.openSession(true),指定事务为主动提交模式,所以最初咱们不须要手动提交事务了。

更新操作

需要:传入 UserModel 对象,而后通过 id 更新数据。

UserMapper.xml 中定义 Update 操作

应用 update 定义更新操作:

<!-- update 用来定义一个更新操作
     id:操作的具体标识
     parameterType:指定操作承受的参数类型
 -->
    <update id="updateUser" parameterType="zhonghu.mybatis.chat01.UserModel">
    <![CDATA[UPDATE user SET name = #{name},age = #{age},salary = #{salary} WHERE id = #{id}
    ]]>
</update>

写法和 insert 操作的写法相似,指定 id 标识、parameterType 指定操作的参数类型,元素体中是具体的 sql 语句。

调用 SqlSession.update 办法执行更新操作

须要调用 SqlSession.update 办法:

int update(String statement, Object parameter)

这个办法有 2 个参数:

  • statement:示意哪个操作,值为 Mapper xml 的 namespace. 具体操作的 id,如须要调用 UserMapper.xml 中的 updateUser 操作,这个值就是:
zhonghu.mybatis.chat01.UserMapper.updateUser
  • parameter:update 操作的参数,和 Mapper xml 中的 update 中的 parameterType 指定的类型统一。

返回值为 update 影响行数。

UserTest 类中新增一个测试用例:

    @Test
    public void updateUser() {try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
            // 创立 UserModel 对象
            UserModel userModel = UserModel.builder().id(1L).name("Java 冢狐,你好").age(18).salary(5000D).build();
            // 执行更新操作
            int result = sqlSession.update("zhonghu.mybatis.chat01.UserMapper.updateUser", userModel);
            log.info("影响行数:{}", result);
        }
    }

运行输入:

12:17.143 [main] DEBUG z.m.chat01.UserMapper.updateUser - ==>  Preparing: UPDATE user SET name = ?,age = ?,salary = ? WHERE id = ? 
12:17.163 [main] DEBUG z.m.chat01.UserMapper.updateUser - ==> Parameters: Java 冢狐,你好(String), 18(Integer), 5000.0(Double), 1(Long)
12:17.258 [main] DEBUG z.m.chat01.UserMapper.updateUser - <==    Updates: 1
12:17.258 [main] INFO  z.mybatis.chat01.UserMapperTest - 影响行数:1

删除操作

需要:依据用户的 id 删除对应的用户记录

UserMapper.xml 中定义 Delete 操作

应用 update 元素定义删除操作:

<!-- update 用来定义一个删除操作
     id:操作的具体标识
     parameterType:指定操作承受的参数类型
 -->
<update id="deleteUser" parameterType="java.lang.Long">
    <![CDATA[DELETE FROM user WHERE id = #{id}
    ]]>
</update>

写法和 update 操作的写法相似,指定 id 标识、parameterType 指定操作的参数类型,用户 id 为 Long 类型的,元素体中是具体的 delete 语句。

调用 SqlSession.update 办法执行更新操作

须要调用 SqlSession.delete 办法:

int delete(String statement, Object parameter)

这个办法有 2 个参数:

  • statement:示意哪个操作,值为 Mapper xml 的 namespace. 具体操作的 id,如须要调用 UserMapper.xml 中的 deleteUser 操作,这个值就是:
com.javacode2018.chat02.UserMapper.
  • parameter:delete 操作的参数,和 Mapper xml 中的 delete 中的 parameterType 指定的类型统一。

返回值为 delete 影响行数。

UserTest 类中新增一个测试用例:

    @Test
    public void deleteUser() {try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
            // 定义须要删除的用户 id
            Long userId = 1L;
            // 执行删除操作
            int result = sqlSession.delete("zhonghu.mybatis.chat01.UserMapper.deleteUser", userId);
            log.info("影响行数:{}", result);
        }
    }

运行输入:

14:26.711 [main] DEBUG z.m.chat01.UserMapper.deleteUser - ==>  Preparing: DELETE FROM user WHERE id = ? 
14:26.729 [main] DEBUG z.m.chat01.UserMapper.deleteUser - ==> Parameters: 1(Long)
14:26.811 [main] DEBUG z.m.chat01.UserMapper.deleteUser - <==    Updates: 1
14:26.812 [main] INFO  z.mybatis.chat01.UserMapperTest - 影响行数:1

执行查问

select 语句有恩多属性能够具体的配置每一条 SQL 语句

  • SQL 语句返回值类型【残缺的类名或者别名】
  • 传入 SQL 语句的参数类型【倡议应用万能的 map】
  • 命名空间中的惟一的标识符
  • 接口中的办法名与映射文件中的 SQL 语句 ID 一一对应

需要:查问所有用户信息

UserMapper.xml 中定义 Select 操作

    <!-- select 用来定义一个查问操作
     id:操作的具体标识
     resultType:指定查问后果保留的类型
 -->
    <select id="getUserList" resultType="zhonghu.mybatis.chat01.UserModel">
    <![CDATA[SELECT * FROM user]]>
</select>

写法和 update 操作的写法相似,指定 id 标识、parameterType 指定操作的参数类型,resultType 指定查问后果的类型,元素体中是具体的 select 语句。

调用 SqlSession.select 办法执行更新操作

UserTest 增加一个用例:

  @Test
    public void getUserList() {try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
            // 执行查问操作
            List<UserModel> userModelList = sqlSession.selectList("zhonghu.mybatis.chat01.UserMapper.getUserList");
            log.info("后果:{}", userModelList);
        }
    }

多插入几行,而后运行下面的用例,输入如下:

16:00.798 [main] DEBUG z.m.chat01.UserMapper.getUserList - ==>  Preparing: SELECT * FROM user 
16:00.817 [main] DEBUG z.m.chat01.UserMapper.getUserList - ==> Parameters: 
16:00.829 [main] DEBUG z.m.chat01.UserMapper.getUserList - <==      Total: 16
16:00.829 [main] INFO  z.mybatis.chat01.UserMapperTest - 后果:[UserModel(id=2, name= 批改冢狐, age=23, salary=50000.0), UserModel(id=3, name= 批改冢狐, age=24, salary=6666.66), UserModel(id=4, name=Mybatis-1, age=19, salary=10000.0), UserModel(id=5, name=Java 冢狐 -2, age=25, salary=20000.0), UserModel(id=6, name=Mybatis-2, age=20, salary=20000.0), UserModel(id=7, name=Java 冢狐 -3, age=26, salary=30000.0), UserModel(id=8, name=Mybatis-3, age=21, salary=30000.0), UserModel(id=9, name=Java 冢狐 -4, age=27, salary=40000.0), UserModel(id=10, name=Mybatis-4, age=22, salary=40000.0), UserModel(id=11, name=Java 冢狐 -5, age=28, salary=50000.0), UserModel(id=12, name=Mybatis-5, age=23, salary=50000.0), UserModel(id=13, name=Java 冢狐, age=1, salary=0.0), UserModel(id=14, name= 冢狐, age=23, salary=50000.0), UserModel(id=59, name=Java 冢狐, age=30, salary=50000.0), UserModel(id=69, name=Java 冢狐, age=30, salary=50000.0), UserModel(id=89, name=Java 冢狐, age=30, salary=50000.0)]

Mapper 接口的应用

为什么须要 Mapper 接口

下面咱们解说了对一个表的增删改查操作,都是通过调用 SqlSession 中的办法来实现的,大家再来看一下 SqlSession 接口中方才用到的几个办法的定义:

int insert(String statement, Object parameter);
int update(String statement, Object parameter);
int delete(String statement, Object parameter);
<E> List<E> selectList(String statement);

这些办法的特点咱们来看一下:

  • 调用这些办法,须要明确晓得 statement 的值,statement 的值为 namespace. 具体操作的 id,这些须要关上 Mapper xml 中去查看了才晓得,写起来不不便
  • parameter 参数都是 Object 类型的,咱们基本不晓得这个操作具体类型是什么,须要查看 Mapper xml 才晓得,轻易传递个值,可能类型不匹配,然而只有在运行的时候才晓得有问题
  • selectList 办法返回的是一个泛型类型的,通过这个办法咱们基本不晓得返回的后果的具体类型,也须要去查看 Mapper xml 才晓得

这些都是应用过程中不不便的状况,想要不便的应用,就须要用到 mybatis 中的 Mapper 接口,咱们能够定义一个 interface,而后和 Mapper xml 关联起来,Mapper xml 中的操作和 Mapper 接口中的办法会进行绑定,当咱们调用 Mapper 接口的办法的时候,会间接调用到 Mapper xml 中的操作,接口的残缺类名须要和 Mapper xml 中的 namespace 统一。

Mapper 接口的用法(三步)

步骤 1:定义 Mapper 接口

去看一下,user.xml 中的 namespace,是:

<mapper namespace="zhonghu.mybatis.chat01.UserMapper">

咱们创立的接口残缺的名称须要和下面的 namespace 的值一样,上面咱们创立一个接口 UserMapper,如下:

UserMapper.xml 中有 4 个操作,咱们须要在 UserMapper 接口中也定义 4 个操作,和 UserMapper.xml 的 4 个操作对应,如下:

package zhonghu.mybatis.chat01;
import java.util.List;

public interface UserMapper {int insertUser(UserModel model);
    int updateUser(UserModel model);
    int deleteUser(Long userId);
    List<UserModel> getUserList();}

UserMapper 接口中定义了 4 个办法,办法的名称须要和 UserMapper.xml 具体操作的 id 值一样,这样调用 UserMapper 接口中的办法的时候,才会对应的找到 UserMapper.xml 中具体的操作。

比方调用 UserMapper 接口中的 insertUser 办法,mybatis 查找的规定是:通过接口残缺名称. 办法名称去 Mapper xml 中找到对应的操作。

步骤 2:通过 SqlSession 获取 Mapper 接口对象

SqlSession 中有个 getMapper 办法,能够传入接口的类型,获取具体的 Mapper 接口对象,如下:

/**
   * Retrieves a mapper.
   * @param <T> the mapper type
   * @param type Mapper interface class
   * @return a mapper bound to this SqlSession
   */
  <T> T getMapper(Class<T> type);

如获取 UserMapper 接口对象:

UserMapper mapper = sqlSession.getMapper(UserMapper.class);

步骤 3:调用 Mapper 接口的办法对 db 进行操作

如调用 UserMapper 接口的 insert 操作:

@Test
public void insertUser() {try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        // 创立 UserModel 对象
        UserModel userModel = UserModel.builder().id(System.currentTimeMillis()).name("Java 冢狐").age(30).salary(50000D).build();
        // 执行插入操作
        int insert = mapper.insertUser(userModel);
        log.info("影响行数:{}", insert);
    }
}

案例:应用 Mapper 接口来实现增删改查

创立一个测试类,代码如下:

package zhonghu.mybatis.chat01;
import lombok.extern.slf4j.Slf4j;
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.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

@Slf4j
public class UserMapperTest {
    private SqlSessionFactory sqlSessionFactory;
    @Before
    public void before() throws IOException {
        // 指定 mybatis 全局配置文件
        String resource = "mybatis-config.xml";
        // 读取全局配置文件
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 构建 SqlSessionFactory 对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        this.sqlSessionFactory = sqlSessionFactory;
    }
    @Test
    public void insertUser() {try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            // 创立 UserModel 对象
            UserModel userModel = UserModel.builder().id(System.currentTimeMillis()).name("Java 冢狐").age(30).salary(50000D).build();
            // 执行插入操作
            int insert = mapper.insertUser(userModel);
            log.info("影响行数:{}", insert);
        }
    }
    @Test
    public void updateUser() {try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            // 创立 UserModel 对象
            UserModel userModel = UserModel.builder().id(1L).name("Java 冢狐,你好").age(18).salary(5000D).build();
            // 执行更新操作
            int result = mapper.updateUser(userModel);
            log.info("影响行数:{}", result);
        }
    }
    @Test
    public void deleteUser() {try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            // 定义须要删除的用户 id
            Long userId = 1L;
            // 执行删除操作
            int result = mapper.deleteUser(userId);
            log.info("影响行数:{}", result);
        }
    }
    @Test
    public void getUserList() {try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            // 执行查问操作
            List<UserModel> userModelList = mapper.getUserList();
            userModelList.forEach(item -> {log.info("{}", item);
            });
        }
    }
}

大家认真看一下下面的代码,这次咱们应用了 UserMapper 来间接调用 UserMapper.xml 中对应的操作,能够去运行一下感受一下成果。

Mapper 接口应用时留神的几点

  • Mapper 接口的残缺类名必须和对应的 Mapper xml 中的 namespace 的值统一
  • Mapper 接口中办法的名称须要和 Mapper xml 中具体操作的 id 值统一
  • Mapper 接口中办法的参数、返回值能够不和 Mapper xml 中的统一

Mapper 接口的原理

这个应用 java 中的动静代理实现的,mybatis 启动的时候会加载全局配置文件 mybatis-config.xml,而后解析这个文件中的 mapper 元素指定的 UserMapper.xml,会依据 UserMapper.xml 的 namespace 的值创立这个接口的一个动静代理,具体能够去看一下 mybatis 的源码,次要应用 java 中的 Proxy 实现的,应用 java.lang.reflect.Proxy 类中的 newProxyInstance 办法,咱们能够创立任意一个接口的一个代理对象:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

咱们应用 Proxy 来模拟 Mapper 接口的实现:

package zhonghu.mybatis.chat01;

import lombok.extern.slf4j.Slf4j;
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.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;

@Slf4j
public class UserMapperTest {
    public static class UserMapperProxy implements InvocationHandler {
        private SqlSession sqlSession;
        private Class<?> mapperClass;
        public UserMapperProxy(SqlSession sqlSession, Class<?> mapperClass) {
            this.sqlSession = sqlSession;
            this.mapperClass = mapperClass;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {log.debug("invoke start");
            String statement = mapperClass.getName() + "." + method.getName();
            List<Object> result = sqlSession.selectList(statement);
            log.debug("invoke end");
            return result;
        }
    }
    private SqlSessionFactory sqlSessionFactory;
    @Before
    public void before() throws IOException {
        // 指定 mybatis 全局配置文件
        String resource = "mybatis-config.xml";
        // 读取全局配置文件
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 构建 SqlSessionFactory 对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        this.sqlSessionFactory = sqlSessionFactory;
    }
    @Test
    public void test1() {try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(UserMapperTest.class.getClassLoader(), new Class[]{UserMapper.class}, new UserMapperProxy(sqlSession, UserMapper.class));
            log.info("{}", userMapper.getUserList());
        }
    }
}

下面代码中:UserMapper 是没有实现类的,能够通过 Proxy.newProxyInstance 给 UserMapper 接口创立一个代理对象,当调用 UserMapper 接口的办法的时候,会调用到 UserMapperProxy 对象的 invoke 办法。

运行一下 test1 用例,输入如下:

29:37.847 [main] DEBUG z.m.chat01.UserMapper.getUserList - ==>  Preparing: SELECT * FROM user 
29:37.865 [main] DEBUG z.m.chat01.UserMapper.getUserList - ==> Parameters: 
29:37.878 [main] DEBUG z.m.chat01.UserMapper.getUserList - <==      Total: 16
29:37.878 [main] DEBUG z.mybatis.chat01.UserMapperTest - invoke end
29:37.878 [main] INFO  z.mybatis.chat01.UserMapperTest - [UserModel(id=2, name= 批改冢狐, age=23, salary=50000.0), UserModel(id=3, name= 批改冢狐, age=24, salary=6666.66), UserModel(id=4, name=Mybatis-1, age=19, salary=10000.0), UserModel(id=5, name=Java 冢狐 -2, age=25, salary=20000.0), UserModel(id=6, name=Mybatis-2, age=20, salary=20000.0), UserModel(id=7, name=Java 冢狐 -3, age=26, salary=30000.0), UserModel(id=8, name=Mybatis-3, age=21, salary=30000.0), UserModel(id=9, name=Java 冢狐 -4, age=27, salary=40000.0), UserModel(id=10, name=Mybatis-4, age=22, salary=40000.0), UserModel(id=11, name=Java 冢狐 -5, age=28, salary=50000.0), UserModel(id=12, name=Mybatis-5, age=23, salary=50000.0), UserModel(id=13, name=Java 冢狐, age=1, salary=0.0), UserModel(id=14, name= 冢狐, age=23, salary=50000.0), UserModel(id=59, name=Java 冢狐, age=30, salary=50000.0), UserModel(id=69, name=Java 冢狐, age=30, salary=50000.0), UserModel(id=89, name=Java 冢狐, age=30, salary=50000.0)]

留神下面输入的 invoke start 和 invoke end,能够看到咱们调用 userMapper.getUserList 时候,被 UserMapperProxy#invoke 办法解决了。

Mybatis 中创立 Mapper 接口代理对象应用的是上面这个类,大家能够去钻研一下:

public class MapperProxyFactory<T> {
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
  public MapperProxyFactory(Class<T> mapperInterface) {this.mapperInterface = mapperInterface;}
  public Class<T> getMapperInterface() {return mapperInterface;}
  public Map<Method, MapperMethod> getMethodCache() {return methodCache;}
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface}, mapperProxy);
  }
  public T newInstance(SqlSession sqlSession) {final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
}

最初

  • 如果感觉看完有播种,心愿能关注一下,顺便给我点个赞,这将会是我更新的最大能源,感激各位的反对
  • 欢送各位关注我的公众号【java 冢狐】,专一于 java 和计算机基础知识,保障让你看完有所播种,不信你打我
  • 求一键三连:点赞、转发、在看。
  • 如果看完有不同的意见或者倡议,欢送多多评论一起交换。感激各位的反对以及厚爱。

——我是冢狐,和你一样酷爱编程。

欢送关注公众号“Java 冢狐”,获取最新消息

退出移动版