第2篇:MyBatis入门

79 阅读9分钟

1. 安装与引用

参考Spring Boot引用MyBatis对应的文章。

2. MyBatis配置

第一篇文章中已经介绍了,MyBatis默认使用PreparedStatement来执行SQL语句,因此,MyBatis需要先获取数据库连接。

2.1 使用mybatis-config.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>
  <!-- 默认指定的数据库环境为MySQL,引用其中一个environment元素的id -->
  <environments default="mysql">
    <!-- 配置名为mysql(名称可任意取)的环境 -->
    <environment id="mysql">
      <!-- 配置事务管理器,JDBC代表使用JDBC自带的事务提交和回滚 -->
      <transactionManager type="JDBC"/>
      <!-- datasource配置数据源,此处使用MyBatis内置的数据源 -->
      <datasource type="POOLED">
        <!-- 配置连接数据库的驱动、URL、用户名和密码 -->
        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/example"/>
        <property name="username" value="root"/>
        <property name="password" value="admin"/>
      </datasource>
    </environment>
  </environments>
  <mappers>
    <!-- 配置MyBatis需要加载的Mapper -->
    <mapper resource="org/example/app/dao/UserMapper.xml"/>
  </mappers>
</configuration>

配置文件主要分为2大部分,也是MyBatis核心配置文件的主要部分:数据库环境(包括数据源以及事务配置)和Mapper。

  1. environments:配置数据库环境,包括驱动、URL、用户名和密码;MyBatis也支持同时配置多个数据库环境,从而保证MyBatis应用可在不同的数据库环境之间切换;

    1.1. transactionManger:用于配置事务管理器

    1.2. dataSoure:用于配置数据源。MyBatis提供了内置的数据源来管理数据库连接,也支持使用第三方插件来管理数据源。数据源会负责维持一个数据库连接池

  2. mappers:该元素用于配置MyBatis需要加载的映射器Mapper。Mapper是MyBatis的核心,用来管理要执行的SQL语句以及将ResultSet转为Java对象。

2.2 Spring Boot中使用application.yml配置

# 配置datasource
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/example
    username: root
    password: admin
# 配置Mapper
mybatis:
  type-aliases-package: com.jiaoxn.example.entity
  mapper-locations: org/example/app/dao/UserMapper.xml

2.3 Spring Boot中使用application.properties配置

# 配置datasource
spring.datasource.driver-class-name: com.mysql.cj.jdbc.Driver
spring.datasource.url: jdbc:mysql://localhost:3306/example
spring.datasource.username: root
spring.datasource.password: admin# 配置Mapper
mybatis.mapper-locations-classpath=org/example/app/dao/UserMapper.xml
mybatis.type-aliases-package: com.jiaoxn.example.entity

3. MyBatis Mapper

上一小节提到的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.app.dao.UserMapper">
  <!-- 用insert元素定义一条insert sql语句,id指定了这条语句的名称 -->
  <insert id="saveUser">
    insert into news_inf values(#{nickname}, #{fullname})
  </insert>
</mapper>

首先,这个文件只有一个根节点:mapper,同时该节点需要提供一个namespace属性,用于指定唯一标识,通常使用Mapper文件所在的包名+文件名称作为该属性值。 接下来,在mapper中有一个insert元素,用来定义一条insert语句,并指定该语句的名称为saveUser。这里定义的insert语句与常见的SQL语句并不一样,这里的insert语句中有#{nickname}#{fullname}2个类似占位符的内容。 前面讲过,MyBatis使用PreparedStatement来执行SQL语句,它会将#{}解析为?,对应的值需要MyBatis传递过来,具体的传递方式有2种:

  • 命名参数:MyBatis执行对应的SQL语句时,传递nickname和fullname两个命名参数(需要使用@Param注解或者Java 8中的命名参数的特性),这样nickname和fullname分别作为#{nickname}#{fullname}的值。

  • 唯一参数:MyBatis执行SQL语句时只传入一个参数,要么是Java对象,要么是Map对象,其中对象中的nicknamefullname的值分别作为#{nickname}#{fullname}的值。

    • 这时有一个特殊情况,如果SQL中只定义了1个参数,并且传给SQL语句时一个标量类型(String、或者8个基本类型或者包装类),MyBatis会将这个值直接传给SQL语句中的参数,并且SQL语句中的参数名称可以任意命名,例如:下面示例中的id可以换成任意的字符传,比如:abcd
<delete id="deleteUser">
  delete from user where id = #{id}
</insert>
// 创建User实例
var userInstance = new HashMap<String, String>();
​
// 设置id
userInstance.put("id", 1);
​
// 调用SqlSession的insert方法
var result = sqlSession.update("org.example.app.dao.UserMapper.deleteUser", 1);
System.out.println(result);
​
// 其他代码
...

4. 数据库CURD操作

借助SqlSession来介绍MyBatis的数据库CURD操作。

使用MyBatis进行持久化操作,通常有如下步骤操作:

  • 在Mapper中定义SQL语句
  • 使用SqlSession执行SQL语句

4.1 Create

首先,需要现在对应的Mapper文件中编写相应的SQL语句。可以参照第3章内容,在UserMapper.xml文件中配置的insert语句。 然后,在Java类中,借助SqlSession来执行SQL语句,具体的代码如下:

public class UserManager {
    public static void createUser(SqlSession sqlSession) {
        // 创建User实例
        var userInstance = new HashMap<String, String>();
​
        // 设置昵称和姓名
        userInstance.put("nickname", "jiaoxn");
        userInstance.put("fullname", "焦向宁");
​
        // 调用SqlSession的insert方法
        var result = sqlSession.insert("org.example.app.dao.UserMapper.saveUser", userInstance);
​
        // 提交事务
        sqlSession.commit();
​
        // 关闭会话
        sqlSession.close();
    }
}

insert方法的第一个参数为org.example.app.dao.UserMapper.saveUserorg.example.app.dao.UserMapperUserMapper.xml文件的唯一标识(namespace),saveUser是insert元素的id值。该参数让MyBatis定位到要执行的SQL语句。 insert方法的第二个参数用于为SQL语句提供参数,这里使用唯一参数的方式,为SQL提供数据。

4.2 Update

同4.1中Insert方法类似,需要在UserMapp.xml中定义相关的SQL语句,具体如下:

<update id="updateUser">
  update user set nickname=#{nickname}, fullname=${fullname} where id = #{id}
</update>

然后,在调用SqlSession中的update方法执行SQL语句。

public class UserManager {
    public static void updateUser(SqlSession sqlSession) {
        // 创建User实例
        var userInstance = new HashMap<String, String>();

        // 设置昵称、姓名和id
        userInstance.put("nickname", "jiaoxn");
        userInstance.put("fullname", "焦向宁");
        userInstance.put("id", 1);

        // 调用SqlSession的update方法
        var result = sqlSession.update("org.example.app.dao.UserMapper.updateUser", userInstance);
        System.out.println(result);

        // 提交事务
        sqlSession.commit();

        // 关闭会话
        sqlSession.close();
    }
}

4.3 Delete

UserMap.xml中定义相关的SQL语句:

<delete id="deleteUser">
  delete from user where id = #{id}
</delete>

调用SqlSession的delete方法执行SQL语句:

public class UserManager {
    public static void deleteUser(SqlSession sqlSession) {
        // 创建User实例
        var userInstance = new HashMap<String, String>();

        // 设置id
        userInstance.put("id", 1);

        // 调用SqlSession的delete方法
        var result = sqlSession.delete("org.example.app.dao.UserMapper.deleteUser", userInstance);
        System.out.println(result);

        // 提交事务
        sqlSession.commit();

        // 关闭会话
        sqlSession.close();
    }
}

4.4 Query

UserMap.xml中定义相关的SQL语句

<select id="selectUser" resultType="map">
  select * from user where id = #{id}
</select>

这里和前面3种情况有些不一样,因为select语句要返回ResultSet,因此需要通过resultType或者resultMap告诉MyBatis将每行记录映射成什么对象。示例中使用的map是MyBatis中的内置对象,告诉MyBatis将每行记录映射为Map。 调用SqlSession的selectOne方法执行SQL语句:

public class UserManager {
    public static void queryUser(SqlSession sqlSession) {
        // 调用SqlSession的query方法
        var result = sqlSession.selectOne("org.example.app.dao.UserMapper.selectUser", 1);
        System.out.println(result);

        // 提交事务
        sqlSession.commit();

        // 关闭会话
        sqlSession.close();
    }
}

SqlSession在查询场景中提供了2个方法:selectOne和selectList。当select语句查询结果只有一行时,使用selectOne方法获取查询结果会更方便;当查询结果有多行时,需要使用selectList方法,此时MyBatis将每行查询结果封装成一个Java对象,然后将所有的Java对象放在List集合中。 MyBatis拿到ResultSet后,需要执行ResultSet映射,具体的底层过程如下:

  1. 开发者需要提前在select元素中定义resultType或者resultMap属性,它们的具体区别在于:

    1. resultType属性直接指定ResultSet每行记录需要映射的类型;
    2. resultMap对应一个<resultMap .../>元素,该元素的type属性同样指定了ResultSet的每行记录要映射的类型,可以支持指定具体的ResultSet的列表和Java对象属性名的映射关系
  2. MyBatis通过反射为结果集的每一行记录创建一个resultType或者resultMap指定的Java对象;

  3. MyBatis根据列名(列别名)与Java对象属性名的对应关系将每行记录的数据设置为对应Java对象的属性值;

  4. 整个ResultSet按照以上规则将所有行转为对应的Java对象,并封装成List集合后返回。

5. 使用Mapper对象

前面第4章的例子,全部使用SqlSession对应的方法来执行SQL语句,虽然简单,但是也存在不足之处,例如:

  • 对数据库的操作都是通过SqlSession执行的,在指定SQL语句时(使用第1个 参数执行SQL语句,第2个参数为SQL语句传值)不太方便,都可能写错;
  • 不能利用Java的类型检查,无法检测selectOne()方法的返回值类型
  • ResultSet只被映射为Map,而不是更有意义的业务领域的对象

接下来使用Mapper对象来改进上述不足之处。

  1. 为领域对象(用户)创建一个Java对象,名称为User.java,所在包:org.exmaple.app.entity。
public class User {
    private Integer id;
    private String nickname;
    private String fullname;

    public User() {}

    public User(String nickname, String fullname) {
        this.nickname = nickname;
        this.fullname = fullname;
    }

    public Integer getId() {
        return id;
    }

    public String getNickname() {
        return nickname;
    }

    public String getFullname() {
        return fullname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public void setFullname(String fullname) {
        this.fullname = fullname;
    }

    public void setId(Integer id) {
        this.id = id;
    }
}
  1. 将UserMapper.xml文件中的查询语句的resultType修改对应的领域类:
<select id="selectUser" resultType="org.exmaple.app.entity.User">
  select * from user where id = #{id}
</select>

这里需要注意的是,如果数据表中的字段与领域类中的属性名称不相同,可以在SQL语句中指定别名,保证别名与领域类中的属性名称相同,例如:

<select id="selectUser" resultType="org.exmaple.app.entity.User">
  select id as is_alias, nickname sd nickname_alias from user where id = #{id}
</select>
  1. 定义一个接口,将Mapper变为DAO组件。接口文件需要遵循以下约定:
  • Mapper接口的接口名称应该与对应的XML文件同名;
  • Mapper接口的源文件应该与对应的XML文件放在同一个包下;
  • Mapper接口的抽象方法应该与XML文件中对应元素的id相同。

接口文件示例如下,文件所在包:org.example.app.dao,文件名称:UserMapper.java

public interface UserMapper {
    int saveUser(User user);
    int updateUser(User user);
    int deleteUser(int userId);
    User queryUser(int userId);
}

开发者只需要按照以上约定定义接口,无需提供实现类,MyBatis会负责为这些接口提供实现类。

  1. 通过调用Mapper来执行数据库操作语句。
public class UserManager {
    public static void createUser(SqlSession sqlSession) {
        // 创建User实例
        var user = new User;

        // 设置昵称和姓名
        user.setNickname("jiaoxn");
        user.setFullname("焦向宁");

        // 获取Mapper对象
        var userMapper = sqlSession.getMapper(UserMapp.class);

        // 调用Mapper对象的方法进行持久化操作
        var result = userMapper.insertUser(user);

        // 提交事务
        sqlSession.commit();

        // 关闭会话
        sqlSession.close();
    }

    public static void updateUser(SqlSession sqlSession) {
        // 创建User实例
        var user = new User;

        // 设置昵称和姓名
        user.setNickname("jiaoxn");
        user.setFullname("焦向宁");
        user.setId(1);

        // 获取Mapper对象
        var userMapper = sqlSession.getMapper(UserMapp.class);

        // 调用Mapper对象的方法进行持久化操作
        var result = userMapper.updateUser(user);
        System.out.println(result);

        // 提交事务
        sqlSession.commit();

        // 关闭会话
        sqlSession.close();
    }

    public static void deleteUser(SqlSession sqlSession) {
        // 获取Mapper对象
        var userMapper = sqlSession.getMapper(UserMapp.class);
        
        // 调用Mapper对象的方法进行持久化操作
        var result = userMapper.deleteUser(1);
        System.out.println(result);

        // 提交事务
        sqlSession.commit();

        // 关闭会话
        sqlSession.close();
    }
    
    public static void queryUser(SqlSession sqlSession) {
        // 获取Mapper对象
        var userMapper = sqlSession.getMapper(UserMapp.class);
        
        // 调用Mapper对象的方法进行持久化操作
        var result = userMapper.queryUser(1);
        System.out.println(result);

        // 提交事务
        sqlSession.commit();

        // 关闭会话
        sqlSession.close();
    }
}

5.1 采用Mapper操作数据库的底层逻辑

  1. MyBatis借助Java动态代理特性(动态代理可以为接口动态生成实现类并保存在内存中),通过反射生成动态代理类。因此,开发者只需要为Mapper组件定义接口,无需提供实现类,MyBatis自动生成动态代理。
  2. MyBatis如何实现接口中定义的方法呢?每个Mapper组件都持有一个SqlSession对象,Mapper组件中的方法主要是如下伪代码。相当于在SqlSession之上包装了一层,可以了以更加面向对象的方式来操作数据。
Class clz = 获取Mapper接口的类;
String statementId = clz.getName() + 当前方法名;

// 执行不同元素定义的SQL语句用对应的方法
return sqlSession.xxx(statementId, 参数);

5.2 使用Mapper方式步骤总结

  1. 根据查询结果定义领域类(或者,实体类)
  2. 开发Mapper组件:Mapper组件 = Mapper.xml + Mapper接口
  3. 调用SqlSession的getMapper方法来获取Mapper组件
  4. 调用Mapper组件的对应方法操作数据库。