【Java架构:基础技术】一篇文章搞掂:MyBatis

 本文篇幅较长,建议合理利用右上角目录进行查看(如果没有目录请刷新)。

本文主要总结于刘增辉的《MyBatisc从入门到精通》一书,有兴趣的朋友可以自行研读

建议仔细研读官方文档:

http://www.mybatis.org/mybatis-3/zh/

http://www.mybatis.org/spring/zh/

http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/

一、MyBatis简介

什么是MyBatis:
  • 一个数据持久化框架
  • 支持定制化 SQL、存储过程以及高级映射
  • 能讲Java的POJO映射为数据库记录
  • Mybatis对比其他持久化框架,优点在于SQL容易控制,方便优化
MyBatis基本原理:
  • 通过XML文件,根据MyBatis提供的语法,编写增删改查的代码
  • 创建实体和接口,与XML文件映射
  • 系统调用实体和接口进行编程
  • 从而实现了SQL语句到实体和接口的映射

二、MyBatis的基本用法

本例子用RBAC(Role-Based Access Control 基于角色的权限访问控制)系统作为例子来介绍MyBatis的XML基本用法:
2.1、先增加几个数据库表
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `user_name` varchar(50) DEFAULT NULL COMMENT '用户名',
  `user_password` varchar(50) DEFAULT NULL COMMENT '密码',
  `user_email` varchar(50) DEFAULT '[email protected]' COMMENT '邮箱',
  `user_info` text COMMENT '简介',
  `head_img` blob COMMENT '头像',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1035 DEFAULT CHARSET=utf8 COMMENT='用户表';

DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
  `role_name` varchar(50) DEFAULT NULL COMMENT '角色名',
  `enabled` int(11) DEFAULT NULL COMMENT '有效标志',
  `create_by` bigint(20) DEFAULT NULL COMMENT '创建人',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='角色表';

DROP TABLE IF EXISTS `sys_privilege`;
CREATE TABLE `sys_privilege` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '权限ID',
  `privilege_name` varchar(50) DEFAULT NULL COMMENT '权限名称',
  `privilege_url` varchar(200) DEFAULT NULL COMMENT '权限URL',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COMMENT='权限表';

DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `role_id` bigint(20) DEFAULT NULL COMMENT '角色ID'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户角色关联表';

DROP TABLE IF EXISTS `sys_role_privilege`;
CREATE TABLE `sys_role_privilege` (
  `role_id` bigint(20) DEFAULT NULL COMMENT '角色ID',
  `privilege_id` bigint(20) DEFAULT NULL COMMENT '权限ID'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色权限关联表';
View Code
2.2、插入测试数据
INSERT INTO `sys_privilege` VALUES ('1', '用户管理', '/users');
INSERT INTO `sys_privilege` VALUES ('2', '角色管理', '/roles');
INSERT INTO `sys_privilege` VALUES ('3', '系统日志', '/logs');
INSERT INTO `sys_privilege` VALUES ('4', '人员维护', '/persons');
INSERT INTO `sys_privilege` VALUES ('5', '单位维护', '/companies');
INSERT INTO `sys_role` VALUES ('1', '管理员', '1', '1', '2016-04-01 17:02:14');
INSERT INTO `sys_role` VALUES ('2', '普通用户', '1', '1', '2016-04-01 17:02:34');
INSERT INTO `sys_role_privilege` VALUES ('1', '1');
INSERT INTO `sys_role_privilege` VALUES ('1', '3');
INSERT INTO `sys_role_privilege` VALUES ('1', '2');
INSERT INTO `sys_role_privilege` VALUES ('2', '4');
INSERT INTO `sys_role_privilege` VALUES ('2', '5');
INSERT INTO `sys_user` VALUES ('1', 'admin', '123456', '[email protected]', '管理员用户', 0x1231231230, '2016-06-07 01:11:12');
INSERT INTO `sys_user` VALUES ('1001', 'test', '123456', '[email protected]', '测试用户', 0x1231231230, '2016-06-07 00:00:00');
INSERT INTO `sys_user_role` VALUES ('1', '1');
INSERT INTO `sys_user_role` VALUES ('1', '2');
INSERT INTO `sys_user_role` VALUES ('1001', '2');
View Code
2.3、创建实体类

开发中创建实体类可以使用MyBatis Generator工具来根据数据库表来生成实体类

package tk.mybatis.simple.model;

import java.io.Serializable;

/**
 * 权限表
 */
public class SysPrivilege implements Serializable {
    private static final long serialVersionUID = 6315662516417216377L;
    /**
     * 权限ID
     */
    private Long id;
    /**
     * 权限名称
     */
    private String privilegeName;
    /**
     * 权限URL
     */
    private String privilegeUrl;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getPrivilegeName() {
        return privilegeName;
    }

    public void setPrivilegeName(String privilegeName) {
        this.privilegeName = privilegeName;
    }

    public String getPrivilegeUrl() {
        return privilegeUrl;
    }

    public void setPrivilegeUrl(String privilegeUrl) {
        this.privilegeUrl = privilegeUrl;
    }

}
View Code
package tk.mybatis.simple.model;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

import tk.mybatis.simple.type.Enabled;

/**
 * 角色表
 */
public class SysRole implements Serializable {
    private static final long serialVersionUID = 6320941908222932112L;
    /**
     * 角色ID
     */
    private Long id;
    /**
     * 角色名
     */
    private String roleName;
    /**
     * 有效标志
     */
    private Enabled enabled;
    /**
     * 创建人
     */
    private String createBy;
    /**
     * 创建时间
     */
    private Date createTime;
    /**
     * 用户信息
     */
    private SysUser user;
    /**
     * 创建信息
     */
    private CreateInfo createInfo;
    
    public CreateInfo getCreateInfo() {
        return createInfo;
    }

    public void setCreateInfo(CreateInfo createInfo) {
        this.createInfo = createInfo;
    }

    /**
     * 角色包含的权限列表
     */
    List privilegeList;

    public List getPrivilegeList() {
        return privilegeList;
    }

    public void setPrivilegeList(List privilegeList) {
        this.privilegeList = privilegeList;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    public Enabled getEnabled() {
        return enabled;
    }

    public void setEnabled(Enabled enabled) {
        this.enabled = enabled;
    }

    public String getCreateBy() {
        return createBy;
    }

    public void setCreateBy(String createBy) {
        this.createBy = createBy;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public SysUser getUser() {
        return user;
    }

    public void setUser(SysUser user) {
        this.user = user;
    }

}
View Code
package tk.mybatis.simple.model;

/**
 * 角色权限关联表
 */
public class SysRolePrivilege {
    /**
     * 角色ID
     */
    private Long roleId;
    /**
     * 权限ID
     */
    private Long privilegeId;

    public Long getRoleId() {
        return roleId;
    }

    public void setRoleId(Long roleId) {
        this.roleId = roleId;
    }

    public Long getPrivilegeId() {
        return privilegeId;
    }

    public void setPrivilegeId(Long privilegeId) {
        this.privilegeId = privilegeId;
    }

}
View Code
package tk.mybatis.simple.model;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

/**
 * 用户表
 */
public class SysUser implements Serializable {
    private static final long serialVersionUID = -328602757171077630L;
    /**
     * 用户ID
     */
    private Long id;
    /**
     * 用户名
     */
    private String userName;
    /**
     * 密码
     */
    private String userPassword;
    /**
     * 邮箱
     */
    private String userEmail;
    /**
     * 简介
     */
    private String userInfo;
    /**
     * 头像
     */
    private byte[] headImg;
    /**
     * 创建时间
     */
    private Date createTime;
    
    /**
     * 用户角色
     */
    private SysRole role;
    
    /**
     * 用户的角色集合
     */
    private List roleList;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserPassword() {
        return userPassword;
    }

    public void setUserPassword(String userPassword) {
        this.userPassword = userPassword;
    }

    public String getUserEmail() {
        return userEmail;
    }

    public void setUserEmail(String userEmail) {
        this.userEmail = userEmail;
    }

    public String getUserInfo() {
        return userInfo;
    }

    public void setUserInfo(String userInfo) {
        this.userInfo = userInfo;
    }

    public byte[] getHeadImg() {
        return headImg;
    }

    public void setHeadImg(byte[] headImg) {
        this.headImg = headImg;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public SysRole getRole() {
        return role;
    }

    public void setRole(SysRole role) {
        this.role = role;
    }

    public List getRoleList() {
        return roleList;
    }

    public void setRoleList(List roleList) {
        this.roleList = roleList;
    }

}
View Code
package tk.mybatis.simple.model;

/**
 * 用户角色关联表
 */
public class SysUserRole {
    /**
     * 用户ID
     */
    private Long userId;
    /**
     * 角色ID
     */
    private Long roleId;

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public Long getRoleId() {
        return roleId;
    }

    public void setRoleId(Long roleId) {
        this.roleId = roleId;
    }

}
View Code
2.4、创建Mapper.xml和对应接口类

在 src/main/resources 的 tk.mybatis.simple.mapper 目录下创建 5 个表各自对应的 XML 文件,分别为 UserMapper.xml 、 RoleMapper.xml 、 PrivilegeMapper.xml 、 UserRoleMapper.xml 和RolePrivilegeMapper.xml 。

在 src/main/java下面创建包 tk.mybatis.simple.mapper 。接着,在该包下创建 XML 文件对应的接口类,分别为 UserMapper.java 、 RoleMapper.java 、 PrivilegeMapper.java 、UserRoleMapper.java 和 RolePrivilegeMapper.java

下面以User表对应的XML文件和接口类设计为例介绍如何实现

UserMapper.java

package tk.mybatis.simple.mapper;

public interface UserMapper {
}
View Code

UserMapper.xml

此文件用全限定的类名,把接口和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="tk.mybatis.simple.mapper.UserMapper">
    
mapper>
View Code
2.5、创建MyBatis配置文件

mybatis-config.xml

把UserMapper配置到mybatis里面去,有2种方法:

1、通过接口配置

<mappers>
    <package name="tk.mybatis.simple.mapper" />
mappers>
View Code

2、通过xml文件配置

<mappers>
    <mapper resource="tk/mybatis/simple/mapper/UserMapper.xml" />
mappers>
View Code

建议通过接口配置,因为只需要指定接口所在的包,MyBatis会扫描所有接口对应的Mapper

这种方式的运行流程如下:

  • 1、判断接口对应的命名空间是否已经存在,如果不存在就抛出异常,存在就继续进行接下来的操作
  • 2、加载接口对应的XML映射文件,将接口全限定名转换为路径,例如,将接口tk.mybatis.simple.mapper.UserMapper转换为tk/mybatis/simple/mapper/UserMapper.xml,以.xml为后缀搜索XML资源,如果找到就解析XML 
  • 3、处理接口中的注解方法

三、MyBatis的XML语法

3.1、select用法

使用纯粹的JDBC时,需要写查询语句,并且对结果集进行手动处理,将结果映射到对象的属性中

使用 MyBatis 时,只需要在XML中添加一个select元素,写一个SQL,再做一些简单的配置,就可以将查询的结果直接映射到对象中

下面以用id查找用户为例,讲解select用法:

1、添加对应接口方法selectById
public interface UserMapper {

    /**
     * 通过 id 查询用户
     * 
     * @param id
     * @return
     */
    SysUser selectById(Long id);
}
View Code
2、添加对应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="tk.mybatis.simple.mapper.UserMapper">
    <resultMap id="userMap" type="tk.mybatis.simple.model.SysUser">
        <id property="id" column="id" />
        <result property="userName" column="user_name" />
        <result property="userPassword" column="user_password" />
        <result property="userEmail" column="user_email" />
        <result property="userInfo" column="user_info" />
        <result property="headImg" column="head_img" jdbcType="BLOB" />
        <result property="createTime" column="create_time" jdbcType="TIMESTAMP" />
    resultMap>

    <select id="selectById" resultMap="userMap">
        select * from sys_user where
        id = #{id}
    select>
mapper>
View Code
  • 接口与XML的关联:通过XML的标签的namespace值设置为接口的全限定名称进行关联
  • 接口中方法与XML的关联:通过XML的标签:

    映射查询语句使用的标签

    • id:命名空间中的唯一标识符,可用来代表这条语句
    • resultMap:用于设置返回值的类型和映射关系
    • resultType:直接自动影射为某对象
    • select * from sys_user where id=#{id} 是查询语句
    • #{id}:MyBatis SQL中使用预编译参数的一种方式,大括号中的 id 是传入的参数名
    • 在上面的select中,使用resultMap设置返回值的类型,这里的userMap就是上面中的id属性值,通过id引用需要的

    标签:

    用于配置Java对象的属性和查询结果列的对应关系,通过resultMap中配置的column和property可以将查询列的值映射到type对象的属性上,因此当我们使用select*查询所有列的时候,MyBatis也可以将结果正确地映射到SysUser对象上

    • id:必填,并且唯一。在select标签中,resultMap指定的值即为此处id所设置的值
    • type:必填,用于配置查询列所映射到的Java对象类型
    • extends:选填,可以配置当前的resultMap继承自其他的resultMap,属性值为继承resultMap的id
    • autoMapping:选填,可选值为true或false,用于是否启用非映射字段(没有在resultMap中的字段)的自动映射功能,该可以覆盖全局的autoMappingBehavior

    下的标签:

    • 标签:配置使用构造方法注入结果,包含以下两个子标签
      • 标签:id参数,标记结果作为id(唯一值),可以帮助提高整体性能。
      • 标签:注入到构造方法的一个普通结果。
    • 标签:一个id结果,标记结果作为id(唯一值),可以帮助提高整体性能。
    • 标签:注入到Java对象属性的普通结果。
    • 的含义相同,不同在于前者是通过类的构造函数注入,后者是通过属性的setter方法注入
    • 标签:一个复杂的类型关联,许多结果将包成这种类型。
    • 标签:复杂类型的集合。
    • 标签:根据结果值来决定使用哪个结果映射。
    • 标签:基于某些值的结果映射。

    里面的属性:

    • column:从数据库中得到的列名,或者是列的别名。
    • property:映射到列结果的属性。可以映射简单的如“username”这样的属性,也可以映射一些复杂对象中的属性,例如“address.street.number”,这会通过“.”方式的属性嵌套赋值。
    • javaType:一个Java类的完全限定名,或一个类型别名(通过typeAlias配置或者默认的类型)。如果映射到一个JavaBean,MyBatis通常可以自动判断属性的类型。如果映射到HashMap,则需要明确地指定javaType属性。
    • jdbcType:列对应的数据库类型。JDBC类型仅仅需要对插入、更新、删除操作可能为空的列进行处理。这是JDBCjdbcType的需要,而不是MyBatis的需要。
    • typeHandler:使用这个属性可以覆盖默认的类型处理器。这个属性值是类的完全限定名或类型别名

    如何定义返回值

    • resultMap:用resultMap来设置映射
    • resultType:根据读取结果和对应类自动映射,可以在select中使用别名,来和类的属性名对应
    5、多结果查询例子

    接口方法

        /**
         * 查询全部用户
         * 
         * @return
         */
        List selectAll();
    View Code

    XML设置

    <select id="selectAll" resultType="tk.mybatis.simple.model.SysUser">
        select id,
        user_name userName,
        user_password userPassword,
        user_email userEmail,
        user_info userInfo,
        head_img headImg,
        create_time createTime
        from sys_user
    select>
    View Code

    这个例子中,select标签中直接使用resultType,以及使用字段别名,使sql的字段名与类的字段名对上,即可自动映射

    6、名称映射规则

    综上,有2种方式:1、在resultMap中配置property属性和column属性的映射;2、SQL中设置别名

    property或者别名要与对象的属性名对应才能匹配,实际匹配时,是把字母都转换成大写来匹配的,所以不区分大小写;一般为了阅读,应该统一写法就OK

    一种很常见的情况,数据库使用下划线命名方式,如user_Name;而Java中使用驼峰式命名,如userName

    MyBatis提供了一个配置,自动将这2种方式进行匹配,在配置文件中设置即可,代码如下

    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    settings>
    View Code
    7、添加单元测试代码

    在src/test/java下的tk.mybatis.simple.mapper包中,添加基础测试类

    package tk.mybatis.simple.mapper;
    
    import java.io.IOException;
    import java.io.Reader;
    
    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.BeforeClass;
    
    /**
     * 基础测试类
     */
    public class BaseMapperTest {
        private static SqlSessionFactory sqlSessionFactory;
        
        @BeforeClass
        public static void init(){
            try {
                Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
                reader.close();
            } catch (IOException ignore) {
                ignore.printStackTrace();
            }
        }
        
        public SqlSession getSqlSession(){
            return sqlSessionFactory.openSession();
        }
        
    }
    View Code

    修改CountryMapperTest类

    因为Country和User的Mapper中都有SelectAll,所以不在唯一,需要用全限定类名tk.mybatis.simple.mapper.CountryMapper.selectAll去调用

    package tk.mybatis.simple.mapper;
    
    import java.util.List;
    
    import org.apache.ibatis.session.SqlSession;
    import org.junit.Test;
    
    import tk.mybatis.simple.model.Country;
    
    public class CountryMapperTest extends BaseMapperTest {
    
        @Test
        public void testSelectAll() {
            SqlSession sqlSession = getSqlSession();
            try {
                List countryList = sqlSession.selectList("tk.mybatis.simple.mapper.CountryMapper.selectAll");
                printCountryList(countryList);
            } finally {
                sqlSession.close();
            }
        }
    
        private void printCountryList(List countryList) {
            for (Country country : countryList) {
                System.out.printf("%-4d%4s%4s\n", country.getId(), country.getCountryname(), country.getCountrycode());
            }
        }
    }
    View Code

     修改mybatis-config.xml,把CountryMapper.xml加入到Mapper配置中

    <mappers>
        <mapper resource="tk/mybatis/simple/mapper/CountryMapper.xml" />
        <package name="tk.mybatis.simple.mapper" />
    mappers>
    View Code

    完成上面设置后即可进行单元测试

    8、一些复杂用法

    a、关联查找某个表的数据

    接口方法

    /**
     * 根据用户 id 获取角色信息
     * 
     * @param userId
     * @return
     */
    List selectRolesByUserId(Long userId);
    View Code

    XML配置

    <select id="selectRolesByUserId" resultType="tk.mybatis.simple.model.SysRole">
        select 
            r.id, 
            r.role_name roleName, 
            r.enabled,
            r.create_by createBy,
            r.create_time createTime,
            u.user_name as "user.userName",
            u.user_email as "user.userEmail"
        from sys_user u
        inner join sys_user_role ur on u.id = ur.user_id
        inner join sys_role r on ur.role_id = r.id
        where u.id = #{userId}
    select>
    View Code

    因为使用了自定义类型Enable,所以在mybatis-config.xml加入自定义类型处理器

    <typeHandlers>
        <typeHandler 
            javaType="tk.mybatis.simple.type.Enabled" 
            handler="tk.mybatis.simple.type.EnabledTypeHandler"/>
    typeHandlers>
    View Code

    这种情况,虽然有关联表查询,但是只是一个实体的数据,所以只是sql中加入了join语句的不同,其它和单个表查询基本相同

    b、关联查询多个表数据

    例如a中,我不仅要查询角色,也要带上用户的信息,那就是要查询出2个表的信息了,那么可以如下这么做

    第一种:把用户的信息增加到角色表中;不过这种方式不建议

    package tk.mybatis.simple.model;
    
    public class SysRoleExtend extends SysRole {
        private String userName;
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName) {
            this.userName = userName;
        }
    
    }
    View Code

    第二种:把user表加到角色表中

    public class SysRole implements Serializable {
        /**
         * 用户信息
         */
        private SysUser user;
    
            ......
    View Code

    修改mapper,使用“.”的方式增加user需要的信息

    View Code

    测试代码

        @Test
        public void testSelectRolesByUserId(){
            SqlSession sqlSession = getSqlSession();
            try {
                UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
                //调用 selectRolesByUserId 方法查询用户的角色
                List roleList = userMapper.selectRolesByUserId(1L);
                //结果不为空
                Assert.assertNotNull(roleList);
                //角色数量大于 0 个
                Assert.assertTrue(roleList.size() > 0);
            } finally {
                //不要忘记关闭 sqlSession
                sqlSession.close();
            }
        }
    View Code

    2.3、MyBatis的XML基本用法——insert用法

    insert比较简单,除了需要返回主键时,不同数据库的方式有所不同

    1、简单用法

    添加接口方法

    /**
     * 新增用户
     * 
     * @param sysUser
     * @return
     */
    int insert(SysUser sysUser);
    View Code

     添加XML设置

    <insert id="insert">
        insert into sys_user(
            user_name, user_password, user_email, 
            user_info, head_img, create_time)
        values(
            #{userName}, #{userPassword}, #{userEmail}, 
            #{userInfo}, #{headImg, jdbcType=BLOB}, #{createTime, jdbcType=TIMESTAMP})
    insert>
    View Code

    测试代码

    @Test
    public void testInsert() {
        SqlSession sqlSession = getSqlSession();
        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            // 创建一个 user 对象
            SysUser user = new SysUser();
            user.setUserName("test1");
            user.setUserPassword("123456");
            user.setUserEmail("[email protected]");
            user.setUserInfo("test info");
            // 正常情况下应该读入一张图片存到 byte 数组中
            user.setHeadImg(new byte[] { 1, 2, 3 });
            user.setCreateTime(new Date());
            // 将新建的对象插入数据库中,特别注意,这里的返回值 result 是执行的 SQL 影响的行数
            int result = userMapper.insert(user);
            // 只插入 1 条数据
            Assert.assertEquals(1, result);
            // id 为 null,我们没有给 id 赋值,并且没有配置回写 id 的值
            Assert.assertNull(user.getId());
        } finally {
            // 为了不影响数据库中的数据导致其他测试失败,这里选择回滚
            // 由于默认的 sqlSessionFactory.openSession() 是不自动提交的,
            // 因此不手动执行 commit 也不会提交到数据库
            sqlSession.rollback();
            // 不要忘记关闭 sqlSession
            sqlSession.close();
        }
    }
    View Code

    标签包含如下属性:

    • id:命名空间中的唯一标识符,可用来代表这条语句。
    • parameterType:即将传入的语句参数的完全限定类名或别名。这个属性是可选的,因为MyBatis可以推断出传入语句的具体参数,因此不建议配置该属性。
    • flushCache:默认值为true,任何时候只要语句被调用,都会清空一级缓存和二级缓存。
    • timeout:设置在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。
    • statementType:对于STATEMENT、PREPARED、CALLABLE,MyBatis会分别使用对应的Statement、PreparedStatement、CallableStatement,默认值为PREPARED。
    • useGeneratedKeys:默认值为false。如果设置为true,MyBatis会使用JDBC的getGeneratedKeys方法来取出由数据库内部生成的主键。
    • keyProperty:MyBatis通过getGeneratedKeys获取主键值后将要赋值的属性名。如果希望得到多个数据库自动生成的列,属性值也可以是以逗号分隔的属性名称列表。
    • keyColumn:仅对INSERT和UPDATE有用。通过生成的键值设置表中的列名,这个设置仅在某些数据库(如PostgreSQL)中是必须的,当主键列不是表中的第一列时需要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
    • databaseId:如果配置了databaseIdProvider(4.6节有详细配置方法),MyBatis会加载所有的不带databaseId的或匹配当前databaseId的语句。如果同时存在带databaseId和不带databaseId的语句,后者会被忽略。
    • 此处中的SQL就是一个简单的INSERT语句,将所有的列都列举出来,在values中通过#{property}方式从参数中取出属性的值。
    • 为了防止类型错误,对于一些特殊的数据类型,建议指定具体的jdbcType值。例如headImg指定BLOB类型,createTime指定TIMESTAMP类型。
    • 特别说明!
    • BLOB对应的类型是ByteArrayInputStream,就是二进制数据流。
    • 由于数据库区分date、time、datetime类型,但是Java中一般都使用java.util.Date类型。因此为了保证数据类型的正确,需要手动指定日期类型,date、time、datetime对应的JDBC类型分别为DATE、TIME、TIMESTAMP。
    2、使用JDBC方式返回主键自增的值

    适用于数据库本身可以设置字段为自增的情况

    接口方法

    /**
     * 新增用户 - 使用 useGeneratedKeys 方式
     * 
     * @param sysUser
     * @return
     */
    int insert2(SysUser sysUser);
    View Code

    XML配置,主要和上面例子增加了useGeneratedKeys="true" keyProperty="id"

    <insert id="insert2" useGeneratedKeys="true" keyProperty="id">
        insert into sys_user(
            user_name, user_password, user_email, 
            user_info, head_img, create_time)
        values(
            #{userName}, #{userPassword}, #{userEmail}, 
            #{userInfo}, #{headImg, jdbcType=BLOB}, #{createTime, jdbcType=TIMESTAMP})
    insert>
    View Code

    useGeneratedKeys设置为true后,MyBatis会使用JDBC的getGeneratedKeys方法来取出由数据库内部生成的主键。获得主键值后将其赋值给keyProperty配置的id属性。当需要设置多个属性时,使用逗号隔开,这种情况下通常还需要设置keyColumn属性,按顺序指定数据库的列,这里列的值会和keyProperty配置的属性一一对应

    测试代码

    @Test
    public void testInsert2() {
        SqlSession sqlSession = getSqlSession();
        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            // 创建一个 user 对象
            SysUser user = new SysUser();
            user.setUserName("test1");
            user.setUserPassword("123456");
            user.setUserEmail("[email protected]");
            user.setUserInfo("test info");
            user.setHeadImg(new byte[] { 1, 2, 3 });
            user.setCreateTime(new Date());
            int result = userMapper.insert2(user);
            // 只插入 1 条数据
            Assert.assertEquals(1, result);
            // 因为 id 回写,所以 id 不为 null
            Assert.assertNotNull(user.getId());
    
        } finally {
            sqlSession.commit();
            // 不要忘记关闭 sqlSession
            sqlSession.close();
        }
    }
    View Code
    3、使用selectKey返回主键的值

    这种方式既适用于2的情况,更适用于数据库不支持自增列,而是通过序列得到一个值,然后赋值给id再插入数据库的情况

    接口方法

    /**
     * 新增用户 - 使用 selectKey 方式
     * 
     * @param sysUser
     * @return
     */
    int insert3(SysUser sysUser);
    View Code

    XML配置

    MySQL数据库

    <insert id="insert3">
        insert into sys_user(
            user_name, user_password, user_email, 
            user_info, head_img, create_time)
        values(
            #{userName}, #{userPassword}, #{userEmail}, 
            #{userInfo}, #{headImg, jdbcType=BLOB}, #{createTime, jdbcType=TIMESTAMP})
        <selectKey keyColumn="id" resultType="long" keyProperty="id" order="AFTER">
            SELECT LAST_INSERT_ID()
        selectKey>
    insert>
    View Code

    Oracle数据库

    
    <insert id="insertOracle">
        <selectKey keyColumn="id" resultType="long" keyProperty="id" order="BEFORE">
            SELECT SEQ_USER.nextval from dual
        selectKey>
        insert into sys_user(
            id, user_name, user_password, user_email, 
            user_info, head_img, create_time)
        values(
            #{id}, #{userName}, #{userPassword}, #{userEmail}, 
            #{userInfo}, #{headImg, jdbcType=BLOB}, #{createTime, jdbcType=TIMESTAMP})
    insert>
    View Code

    selectKey标签的keyColumn、keyProperty和上面useGeneratedKeys的用法含义相同,这里的resultType用于设置返回值类型。order属性的设置和使用的数据库有关。在MySQL数据库中,order属性设置的值是AFTER,因为当前记录的主键值在insert语句执行成功后才能获取到。而在Oracle数据库中,order的值要设置为BEFORE,这是因为Oracle中需要先从序列获取值,然后将值作为主键插入到数据库中

    Oracle方式的INSERT语句中明确写出了id列和值#{id},因为执行selectKey中的语句后id就有值了,我们需要把这个序列值作为主键值插入到数据库中,所以必须指定id列,如果不指定这一列,数据库就会因为主键不能为空而抛出异常

    而selectKey标签的写法在前后并无所谓,影响执行顺序的只是order

    不同数据库生成id的语句不同

    • DB2使用VALUESIDENTITY_VAL_LOCAL()。
    • MYSQL使用SELECTLAST_INSERT_ID()。
    • SQLSERVER使用SELECTSCOPE_IDENTITY()。
    • CLOUDSCAPE使用VALUESIDENTITY_VAL_LOCAL()。
    • DERBY使用VALUESIDENTITY_VAL_LOCAL()。
    • HSQLDB使用CALLIDENTITY()。
    • SYBASE使用SELECT@@IDENTITY。
    • DB2_MF使用SELECTIDENTITY_VAL_LOCAL()FROMSYSIBM.SYSDUMMY1。
    • INFORMIX使用selectdbinfo('sqlca.sqlerrd1')fromsystableswheretabid=1

    2.4、MyBatis的XML基本用法——update用法

    1、简单用法

    接口方法

    /**
     * 根据主键更新
     * 
     * @param sysUser
     * @return
     */
    int updateById(SysUser sysUser);
    View Code

    XML配置

    <update id="updateById">
        update sys_user 
        set user_name = #{userName},
            user_password = #{userPassword},
            user_email = #{userEmail},
            user_info = #{userInfo},
            head_img = #{headImg, jdbcType=BLOB},
            create_time = #{createTime, jdbcType=TIMESTAMP}
        where id = #{id}
    update>
    View Code

    测试代码

    @Test
    public void testUpdateById() {
        SqlSession sqlSession = getSqlSession();
        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            // 从数据库查询 1 个 user 对象
            SysUser user = userMapper.selectById(1L);
            // 当前 userName 为 admin
            Assert.assertEquals("admin", user.getUserName());
            // 修改用户名
            user.setUserName("admin_test");
            // 修改邮箱
            user.setUserEmail("[email protected]");
            // 更新数据,特别注意,这里的返回值 result 是执行的 SQL 影响的行数
            int result = userMapper.updateById(user);
            // 只更新 1 条数据
            Assert.assertEquals(1, result);
            // 根据当前 id 查询修改后的数据
            user = userMapper.selectById(1L);
            // 修改后的名字 admin_test
            Assert.assertEquals("admin_test", user.getUserName());
        } finally {
            // 为了不影响数据库中的数据导致其他测试失败,这里选择回滚
            // 由于默认的 sqlSessionFactory.openSession() 是不自动提交的,
            // 因此不手动执行 commit 也不会提交到数据库
            sqlSession.rollback();
            // 不要忘记关闭 sqlSession
            sqlSession.close();
        }
    }
    View Code

    update就介绍到这里,更复杂的就需要了解后面的动态sql用法了

    2.5、MyBatis的XML基本用法——delete用法

    1、简单用法

    接口方法

    /**
     * 通过主键删除
     * 
     * @param id
     * @return
     */
    int deleteById(Long id);
    
    /**
     * 通过主键删除
     * 
     * @param id
     * @return
     */
    int deleteById(SysUser sysUser);
    View Code

    XML配置

    <delete id="deleteById">
        delete from sys_user where id = #{id}
    delete>
    View Code

    测试代码

    @Test
    public void testDeleteById() {
        SqlSession sqlSession = getSqlSession();
        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            // 从数据库查询 1 个 user 对象,根据 id = 1 查询
            SysUser user1 = userMapper.selectById(1L);
            // 现在还能查询出 user 对象
            Assert.assertNotNull(user1);
            // 调用方法删除
            Assert.assertEquals(1, userMapper.deleteById(1L));
            // 再次查询,这时应该没有值,为 null
            Assert.assertNull(userMapper.selectById(1L));
    
            // 使用 SysUser 参数再做一遍测试,根据 id = 1001 查询
            SysUser user2 = userMapper.selectById(1001L);
            // 现在还能查询出 user 对象
            Assert.assertNotNull(user2);
            // 调用方法删除,注意这里使用参数为 user2
            Assert.assertEquals(1, userMapper.deleteById(user2));
            // 再次查询,这时应该没有值,为 null
            Assert.assertNull(userMapper.selectById(1001L));
            // 使用 SysUser 参数再做一遍测试
        } finally {
            // 为了不影响数据库中的数据导致其他测试失败,这里选择回滚
            // 由于默认的 sqlSessionFactory.openSession() 是不自动提交的,
            // 因此不手动执行 commit 也不会提交到数据库
            sqlSession.rollback();
            // 不要忘记关闭 sqlSession
            sqlSession.close();
        }
    }
    View Code

    delete用法也只讲到这里,更复杂的可学习动态sql

    2.6、多个接口参数的用法

    在前面的例子,所有接口方法都只有一个参数,比如一个string类型的id,或者是一个JavaBean如SysUser

    当要传输多个参数的时候,有2种好用的方法:1、使用Map类型作为参数;2、使用@Param注解

    使用Map类型作为参数的方法,就是在Map中通过key来映射XML中SQL使用的参数值名字,value用来存放参数值,需要多个参数时,通过Map的key-value方式传递参数值,由于这种方式还需要自己手动创建Map以及对参数进行赋值,其实并不简洁,所以对这种方式只做以上简单介绍,接下来着重讲解使用@Param注解的方式

    1、一个错误的例子

    接口方法

    /**
     * 根据用户 id 和 角色的 enabled 状态获取用户的角色
     * 
     * @param userId
     * @param enabled
     * @return
     */
    List selectRolesByUserIdAndRoleEnabled(Long userId, Integer enabled);
    View Code

    XML配置

    <select id="selectRolesByUserIdAndRoleEnabled" resultType="tk.mybatis.simple.model.SysRole">
        select 
            r.id, 
            r.role_name roleName, 
            r.enabled,
            r.create_by createBy,
            r.create_time createTime
        from sys_user u
        inner join sys_user_role ur on u.id = ur.user_id
        inner join sys_role r on ur.role_id = r.id
        where u.id = #{userId} and r.enabled = #{enabled}
    select>
    View Code

    测试代码

    @Test
    public void testSelectRolesByUserIdAndRoleEnabled() {
        SqlSession sqlSession = getSqlSession();
        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            // 调用 selectRolesByUserIdAndRoleEnabled 方法查询用户的角色
            List roleList = userMapper.selectRolesByUserIdAndRoleEnabled(1L, null);
            // 结果不为空
            Assert.assertNotNull(roleList);
            // 角色数量大于 0 个
            Assert.assertTrue(roleList.size() > 0);
        } finally {
            // 不要忘记关闭 sqlSession
            sqlSession.close();
        }
    }
    View Code

    会报错Parameter 'userId' not found. Available parameters are [0, 1, param1, param2]

    这个错误表示,XML可用的参数只有0、1、param1、param2,没有userId。0和1,param1和param2都是MyBatis根据参数位置自定义的名字,这时如果将XML中的#{userId}改为#{0}或#{param1},将#{enabled}改为#{1}或#{param2},这个方法就可以被正常调用了。这样讲只是为了让大家理解它们之间的关系,但实际上并不建议这么做

    2、使用@Param注解

    修改接口方法即可

    /**
     * 根据用户 id 和 角色的 enabled 状态获取用户的角色
     * 
     * @param userId
     * @param enabled
     * @return
     */
    List selectRolesByUserIdAndRoleEnabled(@Param("userId") Long userId, @Param("enabled") Integer enabled);
    View Code

    修改后可以通过测试

    这时的XML文件中对应的SQL的可用参数变成了[userId,enabled,param1,param2],如果把#{userId}改为#{param1},把#{enabled}改为#{param2},测试也可以通过

    给参数配置@Param注解后,MyBatis就会自动将参数封装成Map类型,@Param注解值会作为Map中的key,因此在SQL部分就可以通过配置的注解值来使用参数。

    到这里大家可能会有一个疑问:当只有一个参数(基本类型或拥有TypeHandler配置的类型)的时候,为什么可以不使用注解?这是因为在这种情况下(除集合和数组外),MyBatis不关心这个参数叫什么名字就会直接把这个唯一的参数值拿来使用。

    当参数是JavaBean的时候,如

    /**
     * 根据用户 id 和 角色的 enabled 状态获取用户的角色
     * 
     * @param user
     * @param role
     * @return
     */
    List selectRolesByUserAndRole(@Param("user")SysUser user, @Param("role")SysRole role);
    View Code

    这时,在XML中就不能直接使用#{userId}和#{enabled}了,而是要通过点取值方式使用#{user.id}和#{role.enabled}从两个JavaBean中取出指定属性的值。修改好对应的XML文件后,大家可以自行完善代码并进行测试。
    除了以上常用的参数类型外,接口的参数还可能是集合或者数组,这种类型的参数暂不讨论

    四、动态SQL

    使用ORM的人都知道,根据不同条件拼装SQL时会遇到很多麻烦,例如缺少空格、去掉最后列名的逗号等问题

    MyBatis采用 OGNL(Object-Graph Navigation Language)表达式实现动态SQL,更加简便

    4.1、if标签

    根据标签中的test表达式(下面会详细讲解),如果符合表达式,则拼接在语句中;否则忽略

    用于Where条件
    <select id="selectTestIf" parameterType="com.LTSolution.ShopApp.Model.Product" resultMap="BaseResultMap">
        select * from Product
        where 1=1 
        <if test="name != null and name != ''">
            and Name like '%'+#{name}+'%'
        if>
        <if test="code != null and code != ''">
            and Code like '%'+#{code}+'%'
        if>
        order by code
    select>

    注意:where 1=1 :当2个条件都没有时,如果不加1=1,会产生语句“select * from Product where”,是一个错误语句;可以使用Where标签解决(下面会讲解)

    用于Update更新
    <update id="updateTestIf">
        update Product set
        <if test="name != null and name != ''">
            Name = #{name},
        if>
        <if test="brand != null and brand != ''">
            Brand = #{brand},
        if>
        Code = #{code}
        where Code = #{code}
    update>

    注意:赋值Code = #{code},也是为了防止拼装出来的语句有误;可以使用Where标签或者Set标签解决(下面会讲解)

    用于Insert插入

    动态插入,如果实体的值为空,则不插入,这样可以让字段的值使用数据库的默认值,而不是实体中带的空值。

    <update id="insertTestIf" parameterType="com.LTSolution.ShopApp.Model.Product">
        insert into Product(
        Code,TypeId,
        <if test="brand != null and brand != ''">
            Brand,
        if>
        Name)
        values(
        #{code},#{typeid},
        <if test="brand != null and brand != ''">
            #{brand},
        if>
        #{name}
        )
    update>

     注意,字段和数值都必须有对应的if,否则会报导致数值数量和字段数量不一致

    4.2、choose标签

     此标签提供了类似if...else...的功能;在此标签中必须至少包含一个when,0或1个otherwise

    <select id="selectTestChoose" resultMap="BaseResultMap">
        select * from Product
        where 1=1
        <choose>
        <when test="code != null and code != ''">
            and Code = #{code}
        when>
        <when test="name != null and name != ''">
            and Name = #{name}
        when>
        <otherwise>
            and 1=2
        otherwise>
        choose>
        order by code
    select>

    如果有编号,按编号查找;没有编号,按名称查找;都没有则查不到。

    4.3、where、set 、 trim标签

    他们都用来解决类似拼装时是否去掉第一个and,是否去掉最后一个逗号等问题

    where标签

    where 标签的作用:若标签中有内容,则自动插入一个where,否则不插入任何语句 ;如果 where 后面的字符串是以 AND 和 OR 开头的,就将它们剔除。

    修改上面if标签中where例子,即可省掉where 1=1这个语句

    <select id="selectTestIf" parameterType="com.LTSolution.ShopApp.Model.Product" resultMap="BaseResultMap">
        select * from Product
        <where>
            <if test="name != null and name != ''">
                and Name like '%'+#{name}+'%'
            if>
            <if test="code != null and code != ''">
                and Code = #{code}
            if>
        where>
        order by code
    select>
    set标签

    set标签的作用:若标签中有内容,则自动插入一个set ;如果 set 后面的字符串是以逗号结尾的,就将这个逗号剔除。

    修改上面if标签中update例子如下

    <update id="updateTestIf">
        update Product
        <set>
            <if test="name != null and name != ''">
                Name = #{name},
            if>
            <if test="brand != null and brand != ''">
                Brand = #{brand},
            if>
            Code = #{code},
        set>
        where Code = #{code}
    update>
    trim标签

    where和set标签的功能都可以用trim标签来实现,并且在底层就是通过TrimSqlNode实现的

    • 用trim标签实现where和set

    <select id="selectTestIf" parameterType="com.LTSolution.ShopApp.Model.Product" resultMap="BaseResultMap">
        select * from Product
        <trim prefix="WHERE" prefixOverrides="AND |OR ">
            <if test="name != null and name != ''">
                and Name like '%'+#{name}+'%'
            if>
            <if test="code != null and code != ''">
                and Code = #{code}
            if>
        trim>
        order by code
    select>

    这里的AND和OR后面的空格不能省略,为了避免匹配到andes、orders等单词。

    实际的prefixeOverrides包含“AND”、“OR”、“AND\n”、“OR\n”、“AND\r”、“OR\r”、“AND\t”、“OR\t”,不仅仅是上面提到的两个带空格的前缀。

    <update id="updateTestIf">
        update Product
        <trim prefix="SET" suffixOverrides=",">
            <if test="name != null and name != ''">
                Name = #{name},
            if>
            <if test="brand != null and brand != ''">
                Brand = #{brand},
            if>
            Code = #{code},
        trim>
        where Code = #{code}
    update>
    • trim标签有如下属性
      • prefix:当trim元素内包含内容时,会给内容增加prefix指定的前缀。
      • prefixOverrides:当trim元素内包含内容时,会把内容中匹配的前缀字符串去掉。
      • suffix:当trim元素内包含内容时,会给内容增加suffix指定的后缀。
      • suffixOverrides:当trim元素内包含内容时,会把内容中匹配的后缀字符串去掉。

    4.4、foreach标签

    SQL语句中有时会使用IN关键字,例如id in(1,2,3)。使用${ids}方式直接获取值的写法不能防止SQL注入;使用#{}加上foreach标签的写法,可以避免SQL注入。

    foreach可以对数组、Map或实现了Iterable接口(如List、Set)的对象进行遍历。

    数组在处理时会转换为List对象,因此foreach遍历的对象可以分为两大类:Iterable类型和Map类型。

    • foreach包含以下属性:
      • collection:必填,值为要迭代循环的属性名。这个属性值的情况有很多(下面讲解)。
      • item:变量名,值为从迭代对象中取出的每一个值。
      • index:索引的属性名,在集合数组情况下值为当前索引值,当迭代循环的对象是Map类型时,这个值为Map的key(键值)。
      • open:整个循环内容开头的字符串。
      • close:整个循环内容结尾的字符串。
      • separator:每次循环的分隔符。

    这两种类型在遍历循环时情况不一样,例子如下:

    实现in集合或数组

    例子:使用传入的编号集合查找对应商品

    //接口
    List selectTestForeach(List idList);
    <select id="selectTestForeach" resultMap="BaseResultMap">
        select * from Product
        where code in
        <foreach collection="list" open="(" close=")" separator="," item="id" index="i">
            #{id}
        foreach>
        order by code
    select>
    •  collection的设置:

    • 1、只有一个数组参数或者集合参数
      • 参数会先转换成Map类型,再进行处理
      • 参数为集合时,转换成Map中一个key为colletion的值,此时写colletion="collection"
      • 参数为List时,在上面基础上再添加一个key为list的值,此时写colletion="collection"或colletion="list"
      • 参数为数组时,不同于上面2种情况,会转换成Map中一个key为array的值,此时写colletion="array"
      • 但是推荐使用@Param来指定参数的名称,这样就可以写colletion="参数名"
    • 2、多个参数
      • 使用@param来命名,写colletion="参数名"
    • 3、参数是Map类型
      • 将collection指定为Map中的key
      • 若要循环Map,可以使用@Param,然后colletion="参数名";或直接使用colletion="_parameter"
    • 4、参数是一个对象
      • colletion="对象属性名"
      • 若对象多层嵌套,可以使用属性 . 属性(集合和数组可以使用下标取值)的方式,作为collection的值
    批量插入

    如果数据库支持批量插入,就可以通过foreach来实现。批量插入是SQL-92新增的特性,目前支持的数据库有DB2、SQLServer2008及以上版本、PostgreSQL8.2及以上版本、MySQL、SQLite3.7.11及以上版本、H2。

    sql语法:insert into table1 (c1,c2,c3..) values (v1,v2,v3..),(v1,v2,v3..),(v1,v2,v3..)

    <update id="insertTestForeach">
        insert into Product(Code,TypeId,Name)
        values
        <foreach collection="list" item="product" separator=",">
            (#{product.code},#{product.typeid},#{product.name})
        foreach>
    update>

    通过item指定了循环变量名后,在引用值的时候使用的是“属性.属性”的方式,如product.code

    批量插入还可以返回自增主键id,这里不介绍

    动态update

    介绍当参数类型是Map的时候,如何用foreach进行动态update

    // 接口
    int updateTestForeach(Map map);
    <update id="updateTestForeach">
        update Product set 
        <foreach collection="_parameter" item="val" index="key" separator=",">
            ${key} = #{val}
        foreach>
        where Code = #{code}
    update>

    4.5、bind标签

    bind标签可以使用OGNL表达式创建一个变量并绑定到上下文中

    例如连接函数concat,不同数据库用法不同,可以改成用bind标签的OGNL来表示,即可消除不同数据库不同写法的影响,而且可以防止sql注入

    bind标签有2个属性,name为给上下文引用的变量名,value为OGNL表达式

    <select id="selectTestIf" parameterType="com.LTSolution.ShopApp.Model.Product"
        resultMap="BaseResultMap">
        select * from Product
        <where>
            <if test="name != null and name != ''">
                <bind name="nameLike" value="'%'+name+'%'" />
                and Name like #{nameLike}
            if>
        where>
        order by code
    select>

    4.6、多数据库支持

    使用if标签以及由MyBatis提供的databaseIdProvider数据库厂商标识配置

    MyBatis根据映射语句中的databaseId属性,对不同的数据库执行对应的语句;MyBatis会加载不带databaseId属性以及匹配当前数据库databaseId属性的语句;如果同时找到同id,不带databaseId属性和带databaseId属性的语句,不带databaseId属性的语句会被舍弃掉。

    设置

    首先,需要在mybatis-config.xml文件中加入配置

    <databaseIdProvider type="DB_VENDOR"/>

    在这个设置下,系统通过DatabaseMetaData#getDatabaseProductName()方法获取数据库名称,但是根据不同数据库版本,返回的名称会不同,比较难管理,所以有以下方式

    <databaseIdProvider type="DB_VENDOR">
      <property name="SQL Server" value="sqlserver"/>    
      <property name="DB2" value="db2"/>    
      <property name="Oracle" value="oracle" />
      <property name="MySQL" value="mysql"/>  
      <property name="PostgreSQL" value="postgresql"/>    
      <property name="Derby" value="derby"/>    
      <property name="HSQL" value="hsqldb"/>    
      <property name="H2" value="h2"/>           
    databaseIdProvider>

    通过配置,只要DatabaseMetaData#getDatabaseProductName()的返回值部分字符匹配某个属性的name,就会返回对应的value值

    注意!Spring Boot中不可以使用使用配置文件的形式,应该添加一个Bean

    package com.LTSolution.ShopApp;
    
    import java.util.Properties;
    
    import org.apache.ibatis.mapping.DatabaseIdProvider;
    import org.apache.ibatis.mapping.VendorDatabaseIdProvider;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class MyBatisBean {
        @Bean
        public DatabaseIdProvider getDatabaseIdProvider() {
            DatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();
            Properties properties = new Properties();
            properties.setProperty("Oracle", "oracle");
            properties.setProperty("MySQL", "mysql");
            properties.setProperty("DB2", "db2");
            properties.setProperty("Derby", "derby");
            properties.setProperty("H2", "h2");
            properties.setProperty("HSQL", "hsql");
            properties.setProperty("Informix", "informix");
            properties.setProperty("SQL Server", "sqlserver");
            properties.setProperty("PostgreSQL", "postgresql");
            properties.setProperty("Sybase", "sybase");
            properties.setProperty("Hana", "hana");
            databaseIdProvider.setProperties(properties);
            return databaseIdProvider;
        }
    }
     修改语句

    方式1:对select,insert,delete,update,selectKey,sql标签,设置databaseId属性。MyBatis会根据数据库执行对应的语句

    <select id="selectTestIf" databaseId="mysql">
        ....
    select>
    <select id="selectTestIf" databaseId="sqlserver">
        ....
    select>

     方式2:使用_databaseId参数。为了避免大量重复的SQL,可以使用_databaseId参数进行对不同的部分进行判断。

    <select id="selectTestIf" parameterType="com.LTSolution.ShopApp.Model.Product"
        resultMap="BaseResultMap">
        select * from Product
        <where>
            <if test="name != null and name != ''">
                <if test="_databaseId == 'mysql'">
                    and Name like concat('%'+#{name}+'%')
                if>
                <if test="_databaseId == 'sqlserver'">
                    and Name like '%'+#{name}+'%'
                if>
            if>
        where>
        order by code
    select>

    4.7、OGNL用法

    在MyBatis的动态SQL和${}形式的参数中都用到了OGNL表达式。MyBatis常用的OGNL表达式如下:

    1.e1 or e2
    2.e1 and e2
    3.e1==e2或e1 eq e2
    4.e1 !=e2或e1 neq e2
    5.e1 lt e2:小于
    6.e1 lte e2:小于等于,其他表示为gt(大于)、gte(大于等于)
    7.e1+e2、e1*e2、e1/e2、e1-e2、e1%e2
    8.!e或not e:非,取反
    9.e.method( args ):=调用对象方法
    10.e.property:对象属性值
    11.e1[e2]:按索引取值( List、数组和Map)
    12.@class@method(args):调用类的静态方法
    13.@class@field:调用类的静态字段值

    五、Mybatis插件开发——拦截器(未整理)

    MyBatis允许在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis可以拦截一下4个接口中的几个方法(括号内为该接口的方法名)。

    • Executor(update、query、flushStatements、commit、rollback、getTransaction、close、isClosed)
    • ParameterHandler(getParameterObject、setParameters)
    • ResultSetHandler(handleResultSets、handleCursorResultSets、handleOutputParameters)
    • StatementHandler(prepare、parameterize、batch、update、query)

    这些都是比较底层的类和方法,修改得不好,可能会影响代码执行。所以拦截调用前一定要充分理解。

    5.1、拦截器接口

    MyBatis插件通过实现拦截器接口Interceptor(org.apache.ibatis.plugin.Interceptor),即可在实现类中对拦截对象和方法进行处理。

    拦截器接口Interceptor:
    public interface Interceptor {
      Object intercept(Invocation invocation) throws Throwable;
      Object plugin(Object target);
      void setProperties(Properties properties);
    }

    1、setProperties

    2、plugin

    3、intercept

    5.2、拦截器签名

    通过拦截器注解,来表明这个拦截器需要拦截的接口和方法

    @Intercepts(org.apache.ibatis.plugin.Intercepts)和@Signature(org.apache.ibatis.plugin.Signature)

    @Intercepts注解中的属性是一个@Signature(签名)数组,可以在同一个拦截器中同时拦截不同的接口和方法。

    @Signature注解包含以下三个属性。

      type:设置拦截的接口,可选值是前面提到的4个接口。

      method:设置拦截接口中的方法名,可选值是前面4个接口对应的方法,需要和接口匹配。

      args:设置拦截方法的参数类型数组,通过方法名和参数类型可以确定唯一一个方法。

    0、签名的写法

    type:接口类

    method:方法名称

    args:要拦截的方法的参数类

    下面例子为Executor的update方法的签名写法

    @Intercepts(
            @org.apache.ibatis.plugin.Signature(
                    type = Executor.class, 
                    method = "update", 
                    args = { MappedStatement.class,Object.class }))
    public class ResultSetInterceptor implements Interceptor {
    1、Executor接口

    int update(MappedStatement ms,Object parameter) throws SQLException

    该方法会在所有的 INSERT 、 UPDATE 、 DELETE  执行时被调用,因此如果想要拦截这 3 类操作,可以拦截该方法

    Listquery(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler)throwsSQLException

    该方法会在所有SELECT查询方法执行时被调用。通过这个接口参数可以获取很多有用的信息,因此这是最常被拦截的一个方法。使用该方法需要注意的是,虽然接口中还有一个参数更多的同名接口,但由于MyBatis的设计原因,这个参数多的接口不能被拦截

    Cursor queryCursor(MappedStatement ms,Object parameter,RowBounds rowBounds) throws SQLException

    该方法只有在查询的返回值类型为 Cursor 时被调用。

    List flushStatements() throws SQLException

    该方法只在通过 SqlSession 方法调用 flushStatements 方法或执行的接口方法中带有 @Flush 注解时才被调用,

    void commit(boolean required) throws SQLException

    该方法只在通过 SqlSession 方法调用 commit 方法时才被调用,

    void rollback(boolean required) throws SQLException

    该方法只在通过 SqlSession 方法调用 rollback 方法时才被调用,

    Transaction getTransaction()

    该方法只在通过 SqlSession 方法获取数据库连接时才被调用,

    void close(boolean forceRollback)

    该方法只在延迟加载获取新的 Executor 后才会被执行,

    boolean isClosed()

    该方法只在延迟加载执行查询方法前被执行,

    2、ParameterHandler 接口

    Object getParameterObject()

    该方法只在执行存储过程处理出参的时候被调用。

    void setParameters(PreparedStatement ps) throws SQLException

    该方法在所有数据库方法设置 SQL 参数时被调用。

    3、ResultSetHandler接口

    List handleResultSets(Statement stmt) throws SQLException

    该方法会在除存储过程及返回值类型为Cursor(org.apache.ibatis.cursor.Cursor)以外的查询方法中被调用。

    Cursor handleCursorResultSets(Statement stmt) throws SQLException;

    该方法是 3.4.0 版本中新增加的,只会在返回值类型为 Cursor 的查询方法中被调用

    void handleOutputParameters(CallableStatement cs) throws SQLException;

    该方法只在使用存储过程处理出参时被调用,ResultSetHandler接口的第一个方法对于拦截处理MyBatis的查询结果非常有用,并且由于这个接口被调用的位置在处理二级缓存之前,因此通过这种方式处理的结果可以执行二级缓存。

    4、StatementHandler接口

    Statement prepare(Connection connection,Integer transactionTimeout) throws SQLException

    该方法会在数据库执行前被调用,优先于当前接口中的其他方法而被执行

    void parameterize(Statement statement) throws SQLException

    该方法在prepare方法之后执行,用于处理参数信息

    int batch(Statement statement) throws SQLException

    在全局设置配置defaultExecutorType="BATCH"时,执行数据操作才会调用该方法

    List query(Statement statement,ResultHandler resultHandler) throws SQLException

    执行SELECT方法时调用

    Cursor queryCursor(Statement statement) throws SQLException

    该方法是 3.4.0 版本中新增加的,只会在返回值类型为Cursor的查询中被调用

    5.3、补充,Spring Boot下使用拦截器 

    Spring Boot下需要加入依赖

    <plugin>
        <artifactId>maven-compiler-pluginartifactId>
        <configuration>
            <source>${java.version}source>
            <target>${java.version}target>
        configuration>
    plugin>

     

     

     

     

     

    实例

    1、基于Eclipse+Maven+SqlSession的实例

    此用法比较底层,建议使用基于接口的用法。

    本例子用Eclipse+Maven来创建这个实例,请先掌握Maven的相关知识

    2.1、打开Eclipse,[File]->[New]->[Other],选择Maven Project,然后下一步

    【Java架构:基础技术】一篇文章搞掂:MyBatis_第1张图片

    2、勾上简单项目选项,然后下一步

    【Java架构:基础技术】一篇文章搞掂:MyBatis_第2张图片

    3、填入相关信息,然后点击完成

    【Java架构:基础技术】一篇文章搞掂:MyBatis_第3张图片

     4、得到的项目结构如下

    【Java架构:基础技术】一篇文章搞掂:MyBatis_第4张图片

    5、修改pom文件
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0modelVersion>
        <groupId>tk.mybatisgroupId>
        <artifactId>simpleartifactId>
        <version>0.0.1-SNAPSHOTversion>
    
        <properties>
            <java.version>1.6java.version>
            <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        properties>
    
        <dependencies>
            <dependency>
                <groupId>junitgroupId>
                <artifactId>junitartifactId>
                <version>4.12version>
                <scope>testscope>
            dependency>
            <dependency>
                <groupId>org.mybatisgroupId>
                <artifactId>mybatisartifactId>
                <version>3.3.0version>
            dependency>
            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
                <version>5.1.38version>
            dependency>
            <dependency>
                <groupId>org.slf4jgroupId>
                <artifactId>slf4j-apiartifactId>
                <version>1.7.12version>
            dependency>
            <dependency>
                <groupId>org.slf4jgroupId>
                <artifactId>slf4j-log4j12artifactId>
                <version>1.7.12version>
            dependency>
            <dependency>
                <groupId>log4jgroupId>
                <artifactId>log4jartifactId>
                <version>1.2.17version>
            dependency>
        dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <artifactId>maven-compiler-pluginartifactId>
                    <configuration>
                        <source>${java.version}source>
                        <target>${java.version}target>
                    configuration>
                plugin>
            plugins>
        build>
    project>
    View Code
    6、准备数据库数据
    CREATE DATABASE mybatis DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
    DROP TABLE IF EXISTS `country`;
    CREATE TABLE `country` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `countryname` varchar(255) DEFAULT NULL,
      `countrycode` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
    INSERT INTO `country` VALUES ('1', '中国', 'CN');
    INSERT INTO `country` VALUES ('2', '美国', 'US');
    INSERT INTO `country` VALUES ('3', '俄罗斯', 'RU');
    INSERT INTO `country` VALUES ('4', '英国', 'GB');
    INSERT INTO `country` VALUES ('5', '法国', 'FR');
    View Code
    7、使用XML配置MyBatis

    src/main/resources下创建mybatis-config.xml,注意修改MySql的ip地址和帐号密码

    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>
        <settings>
            <setting name="logImpl" value="LOG4J"/>
        settings>
        
         <typeAliases>
            <package name="tk.mybatis.simple.model"/>
        typeAliases>
    
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC">
                    <property name="" value=""/>
                transactionManager>
                <dataSource type="UNPOOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://192.168.16.137:3306/mybatis"/>
                    <property name="username" value="root"/>
                    <property name="password" value=""/>
                dataSource>
            environment>
        environments>
    
        <mappers>
            <mapper resource="tk/mybatis/simple/mapper/CountryMapper.xml"/>
        mappers>
    configuration>
    View Code

    中的 logImpl 属性配置指定使用 LOG4J 输出日志

    元素下面配置了一个包的别名,通常确定一个类的时候需要使用类的全限定名称,例如 tk.mybatis.simple.model.Country 。在 MyBatis 中需要频繁用到类的全限定名称,为了方便使
    用,我们配置了 tk.mybatis.simple.model 包,这样配置后,在使用类的时候不需要写包名的部分,只使用 Country 即可。

    环境配置中主要配置了数据库连接,数据库的 url 为 jdbc:mysql://localhost:3306/mybatis ,使用的是本机 MySQL 中的 mybatis 数据库,后面的 username 和 password 分别是数据库
    的用户名和密码(如果你的数据库用户名及密码和这里的不一样,请修改为自己数据库可用的用户名和密码)

    中配置了一个包含完整类路径的 CountryMapper.xml ,这是一个 MyBatis 的 SQL 语句和映射配置文件

    8、创建实体类

    在src/main/java下创建包tk.mybatis.simple.model

    然后创建一个类

    package tk.mybatis.simple.model;
    
    public class Country {
        private Long id;
        private String countryname;
        private String countrycode;
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getCountryname() {
            return countryname;
        }
    
        public void setCountryname(String countryname) {
            this.countryname = countryname;
        }
    
        public String getCountrycode() {
            return countrycode;
        }
    
        public void setCountrycode(String countrycode) {
            this.countrycode = countrycode;
        }
    
    }
    View Code
    9、添加Mapper文件

    在src/main/resources下面创建tk/mybatis/simple/mapper目录,再在该目录下面创建CountryMapper.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="tk.mybatis.simple.mapper.CountryMapper">
        <select id="selectAll" resultType="Country">
            select id,countryname,countrycode from country
        select>
    mapper>
    View Code

    SQL 定义在 CountryMapper.xml 文件中,里面的配置作用如下。
    : XML 的根元素,属性 namespace 定义了当前 XML 的命名空间。