MyBatis系列第3篇:Mybatis使用详解(1)

Mybatis系列目标:从入门开始开始掌握一个高级开发所需要的Mybatis技能。

这是mybatis系列第3篇。

主要内容

1、 快速入门

  • 准备数据库

  • 我们的需求

  • 使用idea创建项目

  • pom.xml中引入mybatis依赖

  • 配置mybatis全局配置文件

  • 创建Mapper xml文件

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

  • 构建SqlSessionFactory对象

  • 构建SqlSession对象

  • 引入lombok(非必须)

  • 引入logback支持(非必须)

  • 写一个测试用例

2、 使用SqlSesion执行sql操作

  • SqlSession常见的用法

  • 新增操作

  • 执行删除

  • 执行修改

  • 执行查询

3、 Mapper接口的使用

  • 为什么需要Mapper接口

  • Mapper接口的用法

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

  • Mapper接口使用时注意的几点

  • Mapper接口的原理

4、 案例源码获取方式

快速入门

准备数据库

mysql中执行下面sql:

  1. /*创建数据库javacode2018*/
  2. DROP DATABASE IF EXISTS `javacode2018`;
  3. CREATE DATABASE `javacode2018`;
  4. USE `javacode2018`;
  5. /*创建表结构*/
  6. DROP TABLE IF EXISTS `t_user`;
  7. CREATE TABLE t_user (
  8.   id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键,用户id,自动增长',
  9.   `name` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '姓名',
  10.   `age` SMALLINT NOT NULL DEFAULT 1 COMMENT '年龄',
  11.   `salary` DECIMAL(12,2) NOT NULL DEFAULT 0 COMMENT '薪水',
  12.   `sex` TINYINT NOT NULL DEFAULT 0 COMMENT '性别,0:未知,1:男,2:女'
  13. ) COMMENT '用户表';
  14. SELECT * FROM t_user;

上面我们创建了一个数据库:javacode2018,一个用户表t_user

我们的需求

使用mybatis来实现对t_user表增删改查。

使用idea创建项目

我们在上一篇文章mybatis-series项目中创建另外一个模块chat02,过程如下:

选中mybatis-series,如下图:

点击右键->New->Module,如下图:

选中上图中的Maven,点击Next,如下图:

出现下面窗口:

上图中输入ArtifactIdchat02,点击Next,如下图:

点击上图中的Finish完成chat02模块的创建,项目结构如下图:

pom.xml中引入mybatis依赖

  1. <dependencies>
  2.     <!-- mybatis依赖 -->
  3.     <dependency>
  4.         <groupId>org.mybatis</groupId>
  5.         <artifactId>mybatis</artifactId>
  6.     </dependency>
  7.     <!-- mysql 驱动 -->
  8.     <dependency>
  9.         <groupId>mysql</groupId>
  10.         <artifactId>mysql-connector-java</artifactId>
  11.     </dependency>
  12.     <!-- lombok支持 -->
  13.     <dependency>
  14.         <groupId>org.projectlombok</groupId>
  15.         <artifactId>lombok</artifactId>
  16.     </dependency>
  17.     <!-- 单元测试junit支持 -->
  18.     <dependency>
  19.         <groupId>junit</groupId>
  20.         <artifactId>junit</artifactId>
  21.     </dependency>
  22.     <!-- 引入logback用来输出日志 -->
  23.     <dependency>
  24.         <groupId>ch.qos.logback</groupId>
  25.         <artifactId>logback-classic</artifactId>
  26.     </dependency>
  27. </dependencies>

上面我们引入了依赖mybatis、mysql驱动、lombok支持、junit、logback支持,其实运行mybatis只需要引入下面这一个构件就行了:

  1. <dependency>
  2.     <groupId>org.mybatis</groupId>
  3.     <artifactId>mybatis</artifactId>
  4. </dependency>

注意:上面pom引入的构建中没有写版本号,是因为构件的版本号在父pom.xml中已经声明了,所以chat03/pom.xml中就不需要再去写了。

配置mybatis全局配置文件

使用mybatis操作数据库,那么当然需要配置数据库相关信息,这个需要在mybatis全局配置文件中进行配置。

mybatis需提供一个全局配置的xml文件,可以在这个配置文件中对mybatis进行配置,如事务的支持,数据源的配置等等,这个属于配置文件,我们一般放在main/resource中。

chat03/src/main/resource中创建mybatis-config.xml文件,内容如下:

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE configuration
  3.         PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  4.         "http://mybatis.org/dtd/mybatis-3-config.dtd">
  5. <configuration>
  6.     <!-- 环境配置,可以配置多个环境 -->
  7.     <environments default="chat03">
  8.         <!-- 
  9.             environment用来对某个环境进行配置
  10.             id:环境标识,唯一
  11.          -->
  12.         <environment id="chat03">
  13.             <!-- 事务管理器工厂配置 -->
  14.             <transactionManager type="org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory"/>
  15.             <!-- 数据源工厂配置,使用工厂来创建数据源 -->
  16.             <dataSource type="org.apache.ibatis.datasource.pooled.PooledDataSourceFactory">
  17.                 <property name="driver" value="com.mysql.jdbc.Driver"/>
  18.                 <property name="url" value="jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8"/>
  19.                 <property name="username" value="root"/>
  20.                 <property name="password" value="root123"/>
  21.             </dataSource>
  22.         </environment>
  23.     </environments>
  24. </configuration>

我们做一下解释。

configuration元素

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

environments元素

用来配置mybatis的环境信息,什么是环境?比如开发环境、测试环境、线上环境,这3个环境中的数据库可能是不一样的,可能还有更多的环境。

environments元素中用来配置多个环境的,具体的一个环境使用environment元素进行配置,environment元素有个id用来标识某个具体的环境。

配置了这么多环境,那么mybatis具体会使用哪个呢?

environments元素有个default属性,用来指定默认使用哪个环境,如上面默认使用的是chat03

environment元素

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

  • transactionManager元素

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

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

    一般情况下我们使用org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory这个,mybatis和其他框架集成,比如和spring集成,事务交由spring去控制,spring中有TransactionFactory接口的一个实现org.mybatis.spring.transaction.SpringManagedTransactionFactory,有兴趣的朋友可以去研究一下,这个到时候讲到spring的使用会详细说。

  • dataSource元素

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

    1. org.apache.ibatis.datasource.jndi.JndiDataSourceFactory
    2. org.apache.ibatis.datasource.pooled.PooledDataSourceFactory
    3. org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory

    我们使用第2个org.apache.ibatis.datasource.pooled.PooledDataSourceFactory,这个用来创建一个数据库连接池类型的数据源,可以实现数据库连接共用,减少连接重复创建销毁的时间。

    配置数据源需要指定数据库连接的属性信息,比如:驱动、连接db的url、用户名、密码,这个在dataSource元素下面的property中配置,property元素的格式:

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

创建Mapper xml文件

我们需要对t_user表进行操作,需要写sql,sql写在什么地方呢?

在mybatis中一般我们将一个表的所有sql操作写在一个mapper xml中,一般命名为XXXMapper.xml格式。

创建文件chat02/src/main/resource/mapper/UserMapper.xml,内容如下:

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

mapper xml根元素为mapper,这个元素有个namespace属性,系统中会有很多表,每个表对应一个Mapper xml,为了防止mapper文件重复,我们需要给每个mapper xml文件需要指定一个namespace,通过这个可以区分每个mapper xml文件,上面我们指定为com.javacode2018.chat02.UserMapper

一会对t_user表的所有操作相关的sql,我们都会写在上面这个xml中。

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

UserMapper.xml我们写好了,如何让mybatis知道这个文件呢,此时我们需要在mybatis-config.xml全局配置文件中引入UserMapper.xml,在mybatis-config.xml加入下面配置:

  1. <mappers>
  2.     <mapper resource="mapper/UserMapper.xml" />
  3. </mappers>

mappers元素下面有多个mapper元素,通过mapper元素resource属性可以引入Mapper xml文件,resource是相对于classes的路径。

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

构建SqlSessionFactory对象

  1. //指定mybatis全局配置文件
  2. String resource = "mybatis-config.xml";
  3. //读取全局配置文件
  4. InputStream inputStream = Resources.getResourceAsStream(resource);
  5. //构建SqlSessionFactory对象
  6. 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对象,如下:

  1. //创建一个SqlSession,默认不会自动提交事务
  2. SqlSession openSession();
  3. //创建一个SqlSession,autoCommit:指定是否自动提交事务
  4. SqlSession openSession(boolean autoCommit);

SqlSession接口中很多方法,直接用来操作db,方法清单如下,大家眼熟一下:

  1. <T> T selectOne(String statement);
  2. <T> T selectOne(String statement, Object parameter);
  3. <E> List<E> selectList(String statement);
  4. <E> List<E> selectList(String statement, Object parameter);
  5. <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
  6. <K, V> Map<K, V> selectMap(String statement, String mapKey);
  7. <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
  8. <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
  9. <T> Cursor<T> selectCursor(String statement);
  10. <T> Cursor<T> selectCursor(String statement, Object parameter);
  11. <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
  12. void select(String statement, Object parameter, ResultHandler handler);
  13. void select(String statement, ResultHandler handler);
  14. void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
  15. int insert(String statement);
  16. int insert(String statement, Object parameter);
  17. int update(String statement);
  18. int update(String statement, Object parameter);
  19. int delete(String statement);
  20. int delete(String statement, Object parameter);
  21. void commit();
  22. void commit(boolean force);
  23. void rollback();
  24. void rollback(boolean force);
  25. List<BatchResult> flushStatements();
  26. void close();
  27. void clearCache();
  28. Configuration getConfiguration();
  29. <T> T getMapper(Class<T> type);
  30. Connection getConnection();

上面以select开头的可以对db进行查询操作,insert相关的可以对db进行插入操作,update相关的可以对db进行更新操作。

引入lombok支持(非必须)

声明一下:lombok不是mybatis必须的,为了简化代码而使用的,以后我们会经常使用。

Lombok能以简单的注解形式来简化java代码,提高开发人员的开发效率。例如开发中经常需要写的javabean,都需要花时间去添加相应的getter/setter,也许还要去写构造器、equals等方法,而且需要维护,当属性多时会出现大量的getter/setter方法,这些显得很冗长也没有太多技术含量,一旦修改属性,就容易出现忘记修改对应方法的失误。

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

lombok的使用步骤
  1. 先在idea中安装lombok插件

    打开idea,点击File->Settings->plugins,然后搜索Lombok Plugin,点击安装就可以了。

  2. maven中引入lombok支持

    1. <dependency>
    2.    <groupId>org.projectlombok</groupId>
    3.    <artifactId>lombok</artifactId>
    4.    <version>1.18.10</version>
    5.    <scope>provided</scope>
    6. </dependency>
  3. 代码中使用lombok相关功能

引入logback(非必须)

声明一下:日志框架mybatis中也不是必须的,不用配置也可以正常运行。

为了方便查看mybatis运行过程中产生的日志,比如:执行的sql、sql的参数、sql的执行结果等等调试信息,我们需要引入日志框架的支持,logback是一个很好的日志框架,此处我们就使用这个

mybatis中集成logback步骤
  1. maven中引入logback支持

    1. <dependency>
    2.    <groupId>ch.qos.logback</groupId>
    3.    <artifactId>logback-classic</artifactId>
    4.    <version>1.2.3</version>
    5. </dependency>
  2. src/main/resources中创建logback.xml文件:

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <configuration>
    3.    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    4.        <encoder>
    5.            <pattern>%d{mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    6.        </encoder>
    7.    </appender>
    8.    <logger name="com.javacode2018" level="debug" additivity="false">
    9.        <appender-ref ref="STDOUT" />
    10.    </logger>
    11. </configuration>

logback.xml具体的写法不是本文讨论的范围,有兴趣的朋友可以去研究一下logback具体的用法。

上面xml中配置了com.javacode2018包中所有的类,使用logback输出日志的时候,debug级别及以上级别的日志会输出到控制台,方便我们查看。

写一个测试用例

chat02/src/test下创建一个类:

com.javacode2018.chat02.UserTest

内容如下:

  1. package com.javacode2018.chat02;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.apache.ibatis.io.Resources;
  4. import org.apache.ibatis.session.SqlSession;
  5. import org.apache.ibatis.session.SqlSessionFactory;
  6. import org.apache.ibatis.session.SqlSessionFactoryBuilder;
  7. import org.junit.Before;
  8. import org.junit.Test;
  9. import java.io.IOException;
  10. import java.io.InputStream;
  11. @Slf4j
  12. public class UserTest {
  13.     private SqlSessionFactory sqlSessionFactory;
  14.     @Before
  15.     public void before() throws IOException {
  16.         //指定mybatis全局配置文件
  17.         String resource = "mybatis-config.xml";
  18.         //读取全局配置文件
  19.         InputStream inputStream = Resources.getResourceAsStream(resource);
  20.         //构建SqlSessionFactory对象
  21.         SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  22.         this.sqlSessionFactory = sqlSessionFactory;
  23.     }
  24.     @Test
  25.     public void sqlSession() {
  26.         SqlSession sqlSession = this.sqlSessionFactory.openSession();
  27.         log.info("{}", sqlSession);
  28.     }
  29. }

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

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

运行一下上面的用例:sqlSession方法,输出如下:

45:51.289 [main] INFO  com.javacode2018.chat02.UserTest - org.apache.ibatis.session.defaults.DefaultSqlSession@1f021e6c

使用SqlSesion执行sql操作

SqlSession常见的用法

SqlSession相当于一个连接,可以使用这个对象对db执行增删改查操作,操作完毕之后需要关闭,使用步骤:

  1. 1.获取SqlSession对象:通过该sqlSessionFactory.openSession方法获取SqlSession对象
  2. 2.对db进行操作:使用SqlSession对象进行db操作
  3. 3.关闭SqlSession对象:sqlSession.close();

常见的使用方式如下:

  1. //获取SqlSession
  2. SqlSession sqlSession = this.sqlSessionFactory.openSession();
  3. try {
  4.     //执行业务操作,如:增删改查
  5. } finally {
  6.     //关闭SqlSession
  7.     sqlSession.close();
  8. }

上面我们将SqlSession的关闭放在finally块中,确保close()一定会执行。更简单的方式是使用java中的try()的方式,如下:

  1. try (SqlSession sqlSession = this.sqlSessionFactory.openSession();) {
  2.     //执行业务操作,如:增删改查
  3. }

新增操作

需求:传入UserModel对象,然后将这个对象的数据插入到t_user表中。

创建一个`UserModel`

新建一个com.javacode2018.chat02.UserModel类,代码如下:

  1. package com.javacode2018.chat02;
  2. import lombok.*;
  3. /**
  4.  * 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
  5.  */
  6. @Getter
  7. @Setter
  8. @NoArgsConstructor
  9. @AllArgsConstructor
  10. @Builder
  11. @ToString
  12. public class UserModel {
  13.     private Long id;
  14.     private String name;
  15.     private Integer age;
  16.     private Double salary;
  17.     private Integer sex;
  18. }

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

UserMapper.xml中定义插入操作

我们说过了,对t_user表的所有sql操作,我们都放在UserMapper.xml中,我们在UserMapper.xml中加入下面配置,使用insert元素定义插入操作:

  1. <!-- insert用来定义一个插入操作
  2.      id:操作的具体标识
  3.      parameterType:指定插入操作接受的参数类型
  4.  -->
  5. <insert id="insertUser" parameterType="com.javacode2018.chat02.UserModel">
  6.     <![CDATA[
  7.     INSERT INTO t_user (id,name,age,salary,sex) VALUES (#{id},#{name},#{age},#{salary},#{sex})
  8.      ]]>
  9. </insert>

insert元素用来定义了一个对db的insert操作

id:是这个操作的一个标识,一会通过mybatis执行操作的时候会通过这个namespace和id引用到这个insert操作,

parameterType:用来指定这个insert操作接受的参数的类型,可以是:各种javabean、map、list、collection类型的java对象,我们这个插入接受的是UserModel对象。

insert元素内部定义了具体的sql,可以看到是一个insert的sql,向t_user表插入数据。

需要插入的值从UserModel对象中获取,取UserModel对象的的字段,使用#{字段}这种格式可以获取到UserModel中字段的值。

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

t_user插入的sql我们已经在UserMapper中写好,此时我们怎么调用呢?

需要调用SqlSession.insert方法:

int insert(String statement, Object parameter)

这个方法有2个参数:

statement:表示那个操作,值为Mapper xml的namespace.具体操作的id,如需要调用UserMapper.xml中的insertUser操作,这个值就是:

com.javacode2018.chat02.UserMapper.insertUser

parameter:insert操作的参数,和Mapper xml中的insert中的parameterType指定的类型一致。

返回值为插入的行数。

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

  1. @Test
  2. public void insertUser() {
  3.     try (SqlSession sqlSession = this.sqlSessionFactory.openSession(false);) {
  4.         //创建UserModel对象
  5.         UserModel userModel = UserModel.builder().id(2L).name("javacode2018").age(30).salary(50000D).sex(1).build();
  6.         //执行插入操作
  7.         int result = sqlSession.insert("com.javacode2018.chat02.UserMapper.insertUser", userModel);
  8.         log.info("插入影响行数:{}", result);
  9.         //提交事务
  10.         sqlSession.commit();
  11.     }
  12. }

运行输出如下:

  1. 01:46.683 [main] DEBUG c.j.chat02.UserMapper.insertUser - ==>  Preparing: INSERT INTO t_user (id,name,age,salary,sex) VALUES (?,?,?,?,?) 
  2. 01:46.745 [main] DEBUG c.j.chat02.UserMapper.insertUser - ==> Parameters: 2(Long), javacode2018(String), 30(Integer), 50000.0(Double), 1(Integer)
  3. 01:46.751 [main] DEBUG c.j.chat02.UserMapper.insertUser - <==    Updates: 1
  4. 01:46.751 [main] INFO com.javacode2018.chat02.UserTest - 影响行数:1

输出中打印了详细的sql语句,以及sql的参数信息,可以看到Mapper xml中的#{}被替换为了?,这个使用到了jdbc中的PreparedStatement来对参数设置值。

输出中的第二行详细列出了参数的值以及每个值的类型。

第三行输出了insert的结果为1,表示插入成功了1行记录。

去db中看一下,如下,插入成功:

  1. mysql> SELECT * FROM t_user;
  2. +----+---------------+-----+----------+-----+
  3. | id | name          | age | salary   | sex |
  4. +----+---------------+-----+----------+-----+
  5. |  1 | 路人甲Java    |  30 | 50000.00 |   1 |
  6. +----+---------------+-----+----------+-----+
  7. 1 row in set (0.00 sec)

上面代码中创建SqlSession,我们使用的是sqlSessionFactory.openSession()创建的,这个方法创建的SqlSession,内部事务是非自动提交的方式,所以需要我们手动提交:

sqlSession.commit();

如果想自动提交事务,可以将上面的测试用例改成下面这样:

  1. @Test
  2. public void insertUser() {
  3.     try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
  4.         //创建UserModel对象
  5.         UserModel userModel = UserModel.builder().id(1L).name("路人甲Java").age(30).salary(50000D).sex(1).build();
  6.         //执行插入操作
  7.         int result = sqlSession.insert("com.javacode2018.chat02.UserMapper.insertUser", userModel);
  8.         log.info("影响行数:{}", result);
  9.     }
  10. }

上面在创建SqlSession的时候调用了sqlSessionFactory.openSession(true),指定事务为自动提交模式,所以最后我们不需要手动提交事务了。

更新操作

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

UserMapper.xml中定义Update操作

使用update定义更新操作:

  1. <!-- update用来定义一个更新操作
  2.      id:操作的具体标识
  3.      parameterType:指定操作接受的参数类型
  4.  -->
  5. <update id="updateUser" parameterType="com.javacode2018.chat02.UserModel">
  6.     <![CDATA[
  7.     UPDATE t_user SET name = #{name},age = #{age},salary = #{salary},sex = #{sex} WHERE id = #{id}
  8.     ]]>
  9. </update>

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

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

需要调用SqlSession.update方法:

int update(String statement, Object parameter)

这个方法有2个参数:

statement:表示哪个操作,值为Mapper xml的namespace.具体操作的id,如需要调用UserMapper.xml中的updateUser操作,这个值就是:

com.javacode2018.chat02.UserMapper.updateUser

parameter:update操作的参数,和Mapper xml中的update中的parameterType指定的类型一致。

返回值为update影响行数。

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

  1. @Test
  2. public void updateUser() {
  3.     try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
  4.         //创建UserModel对象
  5.         UserModel userModel = UserModel.builder().id(1L).name("路人甲Java,你好").age(18).salary(5000D).sex(0).build();
  6.         //执行更新操作
  7.         int result = sqlSession.update("com.javacode2018.chat02.UserMapper.updateUser", userModel);
  8.         log.info("影响行数:{}", result);
  9.     }
  10. }

运行输出:

  1. 14:09.051 [main] DEBUG c.j.chat02.UserMapper.updateUser - ==>  Preparing: UPDATE t_user SET name = ?,age = ?,salary = ?,sex = ? WHERE id = ? 
  2. 14:09.095 [main] DEBUG c.j.chat02.UserMapper.updateUser - ==> Parameters: 路人甲Java,你好(String), 18(Integer), 5000.0(Double), 0(Integer), 1(Long)
  3. 14:09.100 [main] DEBUG c.j.chat02.UserMapper.updateUser - <==    Updates: 1
  4. 14:09.101 [main] INFO com.javacode2018.chat02.UserTest - 影响行数:1

db中去看一下:

  1. mysql> SELECT * FROM t_user;
  2. +----+------------------------+-----+----------+-----+
  3. | id | name                   | age | salary   | sex |
  4. +----+------------------------+-----+----------+-----+
  5. |  1 | 路人甲Java,你好       |  18 |  5000.00 |   0 |
  6. |  2 | javacode2018           |  30 | 50000.00 |   1 |
  7. +----+------------------------+-----+----------+-----+
  8. 2 rows in set (0.00 sec)

删除操作

需求:根据用户的id删除对应的用户记录

UserMapper.xml中定义Delete操作

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

  1. <!-- update用来定义一个删除操作
  2.      id:操作的具体标识
  3.      parameterType:指定操作接受的参数类型
  4.  -->
  5. <update id="deleteUser" parameterType="java.lang.Long">
  6.     <![CDATA[
  7.     DELETE FROM t_user WHERE id = #{id}
  8.     ]]>
  9. </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类中新增一个测试用例:

  1. @Test
  2. public void deleteUser() {
  3.     try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
  4.         //定义需要删除的用户id
  5.         Long userId = 1L;
  6.         //执行删除操作
  7.         int result = sqlSession.delete("com.javacode2018.chat02.UserMapper.deleteUser", userId);
  8.         log.info("影响行数:{}", result);
  9.     }
  10. }

运行输出:

  1. 24:45.427 [main] DEBUG c.j.chat02.UserMapper.deleteUser - ==>  Preparing: DELETE FROM t_user WHERE id = ? 
  2. 24:45.476 [main] DEBUG c.j.chat02.UserMapper.deleteUser - ==> Parameters: 1(Long)
  3. 24:45.485 [main] DEBUG c.j.chat02.UserMapper.deleteUser - <==    Updates: 1
  4. 24:45.485 [main] INFO com.javacode2018.chat02.UserTest - 影响行数:1

执行查询

需求:查询所有用户信息

UserMapper.xml中定义Select操作
  1. <!-- select用来定义一个查询操作
  2.      id:操作的具体标识
  3.      resultType:指定查询结果保存的类型
  4.  -->
  5. <select id="getUserList" resultType="com.javacode2018.chat02.UserModel">
  6.     <![CDATA[
  7.     SELECT * FROM t_user
  8.     ]]>
  9. </select>

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

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

UserTest添加一个用例:

  1. @Test
  2. public void getUserList() {
  3.     try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
  4.         //执行查询操作
  5.         List<UserModel> userModelList = sqlSession.selectList("com.javacode2018.chat02.UserMapper.getUserList");
  6.         log.info("结果:{}", userModelList);
  7.     }
  8. }

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

  1. 36:39.015 [main] DEBUG c.j.chat02.UserMapper.getUserList - ==>  Preparing: SELECT * FROM t_user 
  2. 36:39.048 [main] DEBUG c.j.chat02.UserMapper.getUserList - ==> Parameters: 
  3. 36:39.066 [main] DEBUG c.j.chat02.UserMapper.getUserList - <==      Total: 3
  4. 36:39.067 [main] INFO  com.javacode2018.chat02.UserTest - UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1)
  5. 36:39.069 [main] INFO  com.javacode2018.chat02.UserTest - UserModel(id=1575621274235, name=路人甲Java, age=30, salary=50000.0, sex=1)
  6. 36:39.069 [main] INFO  com.javacode2018.chat02.UserTest - UserModel(id=1575621329823, name=路人甲Java, age=30, salary=50000.0, sex=1)

Mapper接口的使用

为什么需要Mapper接口

上面我们讲解了对一个表的增删改查操作,都是通过调用SqlSession中的方法来完成的,大家再来看一下SqlSession接口中刚才用到的几个方法的定义:

  1. int insert(String statement, Object parameter);
  2. int update(String statement, Object parameter);
  3. int delete(String statement, Object parameter);
  4. <E> List<E> selectList(String statement);

这些方法的特点我们来看一下:

  1. 调用这些方法,需要明确知道statement的值,statement的值为namespace.具体操作的id,这些需要打开Mapper xml中去查看了才知道,写起来不方便

  2. parameter参数都是Object类型的,我们根本不知道这个操作具体类型是什么,需要查看Mapper xml才知道,随便传递个值,可能类型不匹配,但是只有在运行的时候才知道有问题

  3. selectList方法返回的是一个泛型类型的,通过这个方法我们根本不知道返回的结果的具体类型,也需要去查看Mapper xml才知道

以上这几点使用都不是太方便,有什么方法能解决上面这些问题么?

有,这就是mybatis中的Mapper接口,我们可以定义一个interface,然后和Mapper xml关联起来,Mapper xml中的操作和Mapper接口中的方法会进行绑定,当我们调用Mapper接口的方法的时候,会间接调用到Mapper xml中的操作,接口的完整类名需要和Mapper xml中的namespace一致。

Mapper接口的用法(三步)

步骤1:定义Mapper接口

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

<mapper namespace="com.javacode2018.chat02.UserMapper">

我们创建的接口完整的名称需要和上面的namespace的值一样,下面我们创建一个接口com.javacode2018.chat02.UserMapper,如下:

  1. package com.javacode2018.chat02;
  2. /**
  3.  * 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
  4.  */
  5. public interface UserMapper {
  6. }

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

  1. package com.javacode2018.chat02;
  2. import java.util.List;
  3. /**
  4.  * 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
  5.  */
  6. public interface UserMapper {
  7.     int insertUser(UserModel model);
  8.     int updateUser(UserModel model);
  9.     int deleteUser(Long userId);
  10.     List<UserModel> getUserList();
  11. }

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

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

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

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

  1.   /**
  2.    * Retrieves a mapper.
  3.    * @param <T> the mapper type
  4.    * @param type Mapper interface class
  5.    * @return a mapper bound to this SqlSession
  6.    */
  7.   <T> T getMapper(Class<T> type);

如获取UserMapper接口对象:

UserMapper mapper = sqlSession.getMapper(UserMapper.class);
步骤3:调用Mapper接口的方法对db进行操作

如调用UserMapper接口的insert操作:

  1. @Test
  2. public void insertUser() {
  3.     try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
  4.         UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  5.         //创建UserModel对象
  6.         UserModel userModel = UserModel.builder().id(System.currentTimeMillis()).name("路人甲Java").age(30).salary(50000D).sex(1).build();
  7.         //执行插入操作
  8.         int insert = mapper.insertUser(userModel);
  9.         log.info("影响行数:{}", insert);
  10.     }
  11. }

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

chat02/src/test/java中创建一个测试类,代码如下:

  1. package com.javacode2018.chat02;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.apache.ibatis.io.Resources;
  4. import org.apache.ibatis.session.SqlSession;
  5. import org.apache.ibatis.session.SqlSessionFactory;
  6. import org.apache.ibatis.session.SqlSessionFactoryBuilder;
  7. import org.junit.Before;
  8. import org.junit.Test;
  9. import java.io.IOException;
  10. import java.io.InputStream;
  11. import java.util.List;
  12. /**
  13.  * 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
  14.  */
  15. @Slf4j
  16. public class UserMapperTest {
  17.     private SqlSessionFactory sqlSessionFactory;
  18.     @Before
  19.     public void before() throws IOException {
  20.         //指定mybatis全局配置文件
  21.         String resource = "mybatis-config.xml";
  22.         //读取全局配置文件
  23.         InputStream inputStream = Resources.getResourceAsStream(resource);
  24.         //构建SqlSessionFactory对象
  25.         SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  26.         this.sqlSessionFactory = sqlSessionFactory;
  27.     }
  28.     @Test
  29.     public void insertUser() {
  30.         try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
  31.             UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  32.             //创建UserModel对象
  33.             UserModel userModel = UserModel.builder().id(System.currentTimeMillis()).name("路人甲Java").age(30).salary(50000D).sex(1).build();
  34.             //执行插入操作
  35.             int insert = mapper.insertUser(userModel);
  36.             log.info("影响行数:{}", insert);
  37.         }
  38.     }
  39.     @Test
  40.     public void updateUser() {
  41.         try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
  42.             UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  43.             //创建UserModel对象
  44.             UserModel userModel = UserModel.builder().id(1L).name("路人甲Java,你好").age(18).salary(5000D).sex(0).build();
  45.             //执行更新操作
  46.             int result = mapper.updateUser(userModel);
  47.             log.info("影响行数:{}", result);
  48.         }
  49.     }
  50.     @Test
  51.     public void deleteUser() {
  52.         try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
  53.             UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  54.             //定义需要删除的用户id
  55.             Long userId = 1L;
  56.             //执行删除操作
  57.             int result = mapper.deleteUser(userId);
  58.             log.info("影响行数:{}", result);
  59.         }
  60.     }
  61.     @Test
  62.     public void getUserList() {
  63.         try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
  64.             UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  65.             //执行查询操作
  66.             List<UserModel> userModelList = mapper.getUserList();
  67.             userModelList.forEach(item -> {
  68.                 log.info("{}", item);
  69.             });
  70.         }
  71.     }
  72. }

大家认真看一下上面的代码,这次我们使用了UserMapper来间接调用UserMapper.xml中对应的操作,可以去运行一下感受一下效果。

Mapper接口使用时注意的几点

  1. Mapper接口的完整类名必须和对应的Mapper xml中的namespace的值一致

  2. Mapper接口中方法的名称需要和Mapper xml中具体操作的id值一致

  3. Mapper接口中方法的参数、返回值可以不和Mapper xml中的一致

Mapper接口的原理

这个使用java中的动态代理实现的,mybatis启动的时候会加载全局配置文件mybatis-config.xml,然后解析这个文件中的mapper元素指定的UserMapper.xml,会根据UserMapper.xml的namespace的值创建这个接口的一个动态代理,具体可以去看一下mybatis的源码,主要使用java中的Proxy实现的,使用java.lang.reflect.Proxy类中的newProxyInstance方法,我们可以创建任意一个接口的一个代理对象:

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

我们使用Proxy来模仿Mapper接口的实现:

  1. package com.javacode2018.chat02;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.apache.ibatis.io.Resources;
  4. import org.apache.ibatis.session.SqlSession;
  5. import org.apache.ibatis.session.SqlSessionFactory;
  6. import org.apache.ibatis.session.SqlSessionFactoryBuilder;
  7. import org.junit.Before;
  8. import org.junit.Test;
  9. import java.io.IOException;
  10. import java.io.InputStream;
  11. import java.lang.reflect.InvocationHandler;
  12. import java.lang.reflect.Method;
  13. import java.lang.reflect.Proxy;
  14. import java.util.List;
  15. /**
  16.  * 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
  17.  */
  18. @Slf4j
  19. public class ProxyTest {
  20.     public static class UserMapperProxy implements InvocationHandler {
  21.         private SqlSession sqlSession;
  22.         private Class<?> mapperClass;
  23.         public UserMapperProxy(SqlSession sqlSession, Class<?> mapperClass) {
  24.             this.sqlSession = sqlSession;
  25.             this.mapperClass = mapperClass;
  26.         }
  27.         @Override
  28.         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  29.             log.debug("invoke start");
  30.             String statement = mapperClass.getName() + "." + method.getName();
  31.             List<Object> result = sqlSession.selectList(statement);
  32.             log.debug("invoke end");
  33.             return result;
  34.         }
  35.     }
  36.     private SqlSessionFactory sqlSessionFactory;
  37.     @Before
  38.     public void before() throws IOException {
  39.         //指定mybatis全局配置文件
  40.         String resource = "mybatis-config.xml";
  41.         //读取全局配置文件
  42.         InputStream inputStream = Resources.getResourceAsStream(resource);
  43.         //构建SqlSessionFactory对象
  44.         SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  45.         this.sqlSessionFactory = sqlSessionFactory;
  46.     }
  47.     @Test
  48.     public void test1() {
  49.         try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
  50.             UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(ProxyTest.class.getClassLoader(), new Class[]{UserMapper.class}, new UserMapperProxy(sqlSession, UserMapper.class));
  51.             log.info("{}", userMapper.getUserList());
  52.         }
  53.     }
  54. }

上面代码中:UserMapper是没有实现类的,可以通过Proxy.newProxyInstance给UserMapper接口创建一个代理对象,当调用UserMapper接口的方法的时候,会调用到UserMapperProxy对象的invoke方法。

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

  1. 16:34.288 [main] DEBUG com.javacode2018.chat02.ProxyTest - invoke start
  2. 16:34.555 [main] DEBUG c.j.chat02.UserMapper.getUserList - ==>  Preparing: SELECT * FROM t_user 
  3. 16:34.580 [main] DEBUG c.j.chat02.UserMapper.getUserList - ==> Parameters: 
  4. 16:34.597 [main] DEBUG c.j.chat02.UserMapper.getUserList - <==      Total: 4
  5. 16:34.597 [main] DEBUG com.javacode2018.chat02.ProxyTest - invoke end
  6. 16:34.597 [main] INFO  com.javacode2018.chat02.ProxyTest - [UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1), UserModel(id=1575621274235, name=路人甲Java, age=30, salary=50000.0, sex=1), UserModel(id=1575621329823, name=路人甲Java, age=30, salary=50000.0, sex=1), UserModel(id=1575623283897, name=路人甲Java, age=30, salary=50000.0, sex=1)]

注意上面输出的invoke startinvoke end,可以看到我们调用userMapper.getUserList时候,被UserMapperProxy#invoke方法处理了。

Mybatis中创建Mapper接口代理对象使用的是下面这个类,大家可以去研究一下:

  1. public class MapperProxyFactory<T> {
  2.   private final Class<T> mapperInterface;
  3.   private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
  4.   public MapperProxyFactory(Class<T> mapperInterface) {
  5.     this.mapperInterface = mapperInterface;
  6.   }
  7.   public Class<T> getMapperInterface() {
  8.     return mapperInterface;
  9.   }
  10.   public Map<Method, MapperMethod> getMethodCache() {
  11.     return methodCache;
  12.   }
  13.   @SuppressWarnings("unchecked")
  14.   protected T newInstance(MapperProxy<T> mapperProxy) {
  15.     return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  16.   }
  17.   public T newInstance(SqlSession sqlSession) {
  18.     final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
  19.     return newInstance(mapperProxy);
  20.   }
  21. }

案例代码获取方式

扫码添加微信备注:mybatis案例,即可获取

MyBatis系列

  1. MyBatis系列第1篇:MyBatis未出世之前我们那些痛苦的经历

  2. MyBatis系列第2篇:入门篇,带你感受一下mybatis独特的魅力!

更多好文章

  1. Java高并发系列(共34篇)

  2. MySql高手系列(共27篇)

  3. Maven高手系列(共10篇)

  4. 聊聊db和缓存一致性常见的实现方式

  5. 接口幂等性这么重要,它是什么?怎么实现?

感谢大家的阅读,也欢迎您把这篇文章分享给更多的朋友一起阅读!谢谢!

路人甲java

▲长按图片识别二维码关注

路人甲Java:工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!来源:https://itsoku.blog.csdn.net/article/details/103431747

posted @ 2022-04-23 02:59  程序员小明1024  阅读(34)  评论(0编辑  收藏  举报