Mybatis整理


概念

Mybatis是Java的持久层框架,是对jdbc的封装,它使得开发者只需要关注sql本身,而不需要花费精力在注册驱动、创建连接、创建statement、设置参数、解析结果等原生jdbc需要完成的操作上。

项目地址:https://github.com/mybatis/mybatis-3

架构

Mybatis的整体架构如下(参考https://blog.csdn.net/hellozpc/article/details/80878563):

mybatis.png

  • SqlMapConfig.xml:全局配置文件,配置运行环境,包括数据源和事物,以及映射文件mapper.xml;
  • SqlSessionFactory:会话工厂,通过全局配置文件可以创建,用于创建SqlSession;
  • SqlSession:面向用户的接口,操作数据库;
  • Executor:执行器 (接口),SqlSession底层使用执行器操作数据库,有两个实现类:默认执行器缓存执行器

  • MappedStatement:Mybatis的封装对象,封装sql语句,包含输入和输出映射;

入门案例

引入依赖:

<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>

    <groupId>cn.adoredu</groupId>
    <artifactId>mybatis-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.2.7</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.9</version>
        </dependency>
        
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

PO类:

package cn.adoredu.mybatis.po;

import java.util.Date;

public class User {

    private int id;
    private String username;
    private String sex;
    private Date birthday;
    private String address;

    public int getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
    
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", sex='" + sex + '\'' +
                ", birthday=" + birthday +
                ", address='" + address + '\'' +
                '}';
    }
}

全局配置文件:

<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!-- 和spring整合后 environments配置将废除-->
    <environments default="development">
        <environment id="development">
            <!-- 使用jdbc事务管理-->
            <transactionManager type="JDBC" />
            <!-- 数据库连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis-demo"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 加载mapper.xml -->
    <mappers>
        <mapper resource="sqlmap/User.xml" />
    </mappers>

</configuration>

映射文件:

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

<!-- 命名空间,方便对sql进行统一管理。 -->
<mapper namespace="usermapper">

    <!-- mapper.xml下面封装多个sql,执行每个sql时都封装为MappedStatement对象。mapper.xml以statement为单位管理sql语句 -->
    <!--
        id:statement的唯一标识
        #{}:表示一个占位符。当输入类型为简单类型时,里面名称任意
        parameterType:输入参数的类型
        resultType:输出结果的类型(list和单条都是单个po类型)
    -->
    <select id="findUserById" parameterType="int" resultType="cn.adoredu.mybatis.po.User">
        SELECT * FROM user WHERE id = ${value}
    </select>

    <select id="findUserByName" parameterType="java.lang.String" resultType="cn.adoredu.mybatis.po.User">
        SELECT * FROM user WHERE username like '%${value}%'
    </select>

    <!--
        #{username}表示从parameterType中获取pojo属性值(OGNL解析),这里使用${}也可以
        selectKey:用于主键返回
        order:设置selectKey中sql相对于insert语句的执行顺序
        keyProperty:主键字段
        resultType:select LAST_INSERT_ID()的类型
    -->
    <insert id="insertUser" parameterType="cn.adoredu.mybatis.po.User">
        <selectKey keyProperty="id" order="AFTER" resultType="int">
            SELECT LAST_INSERT_ID()
        </selectKey>
        INSERT INTO user(username, birthday, sex, address) VALUES(#{username}, #{birthday}, #{sex}, #{address})
    </insert>

    <!-- 使用uuid主键的插入方式-->
    <!--
    <insert id="insertUser" parameterType="cn.adoredu.mybatis.po.User">
        <selectKey keyProperty="id" order="BEFORE" resultType="String">
            SELECT uuid()
        </selectKey>
        INSERT INTO user(id,username,birthday,sex,address) VALUES (#{id}, #{username}, #{birthday}, #{sex}, #{address})
    </insert>
    -->

    <delete id="deleteById" parameterType="int">
        DELETE FROM user WHERE id = #{id}
    </delete>

    <!-- 传入的User对象必须包含id属性 -->
    <update id="updateUser" parameterType="cn.adoredu.mybatis.po.User">
        UPDATE user SET username=#{username}, birthday=#{birthday}, sex=#{sex}, address=#{address} WHERE id=#{id}
    </update>

</mapper>

测试类:

package cn.adoredu.myBatis;

import cn.adoredu.mybatis.po.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.List;

public class UserTest {

    private SqlSessionFactory sqlSessionFactory;

    // 创建工厂
    @Before
    public void init() throws IOException {
        String resource = "SqlMapConfig.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }

    // 根据id查询用户(单条记录)
    @Test
    public void testOne() {
        // 创建SqlSesion
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 操作数据库 1. statement位置(namespace.id),2. 传入的参数
        User user = null;
        try {
            user = sqlSession.selectOne("usermapper.findUserById", 1);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            sqlSession.close();
        }
        System.out.println(user);
    }

    // 根据名称查询(多条记录)
    @Test
    public void testList() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        List<User> userList = null;
        try {
            userList = sqlSession.selectList("usermapper.findUserByName", "admin");
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            sqlSession.close();
        }
        System.out.println(userList);
    }

    @Test
    public void testInsert() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        User user = new User();
        user.setUsername("Tom");
        user.setAddress("sz");
        user.setBirthday(new Date());
        user.setSex("1");
        try {
            sqlSession.insert("usermapper.insertUser", user);
            sqlSession.commit();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            sqlSession.close();
        }
        System.out.println(user.getId());
    }

    @Test
    public void testDelete() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            sqlSession.delete("usermapper.deleteById", 28);
            sqlSession.commit();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            sqlSession.close();
        }
    }

    @Test
    public void testUpdate() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        User user = new User();
        user.setId(27);
        user.setBirthday(new Date());
        user.setSex("2");
        user.setUsername("Jerry");
        user.setAddress("bj");
        try {
            sqlSession.update("usermapper.updateUser", user);
            sqlSession.commit();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            sqlSession.close();
        }
    }
}

注意:

  • #{}:表示占位符,mybatis自动进行参数的java类型到jdbc类型的转换(如字符串时自动添加引号)。输入参数为简单类型时,#{}里面名称任意;
  • ${}:表示sql拼接,使用${}时会将参数不加任何修饰的传入sql(这种方式无法防止sql注入)。输入参数为简单类型时,${}里面只能是value;

步骤总结:

  1. 编写全局配置文件SqlMapConfig.xml(名称不固定);
  2. 编写Mapper.xml,定义statement;
  3. 通过配置文件创建SqlSessionFactory;
  4. 通过SqlSessionFactory获取SqlSession;
  5. 通过SqlSession操作数据库,包括增删改要执行commit;
  6. SqlSession.close();

Mapper代理开发方式

实际项目中,如果Dao的实现类采用上面方式进行开发,实际上仍有大量重复代码。Mybatis提供Mapper代理的开发方式,代码中只需要按一定规则编码Mapper接口和Mapper.xml,Mybatis自动创建接口代理类来完成相应功能。配置文件一般放在mapper目录下,如mapper/UserMapper.xml。规则包括:

  1. Mapper.xml中namespace为Mapper接口的全限定名(全路径);
  2. Mapper.xml中statement的id为Mapper接口的方法名;
  3. Mapper.xml中statement的parameterType和Mapper接口方法的输入参数类型一致;
  4. Mapper.xml的statement的resultType和Mapper接口方法的返回值类型一致;

使用方式:使用sqlSession.getMapper(UserMapper.class)获得代理对象,调用代理对象的方法即可。

映射文件:

<?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="cn.adoredu.mybatis.mapper.UserMapper">

    <select id="findUserById" parameterType="int" resultType="cn.adoredu.mybatis.po.User">
        SELECT * FROM user WHERE id = ${value}
    </select>

    <select id="findUserByName" parameterType="java.lang.String" resultType="cn.adoredu.mybatis.po.User">
        SELECT * FROM user WHERE username like '%${value}%'
    </select>

    <insert id="insertUser" parameterType="cn.adoredu.mybatis.po.User">
        <selectKey keyProperty="id" order="AFTER" resultType="int">
            SELECT LAST_INSERT_ID()
        </selectKey>
        INSERT INTO user(username, birthday, sex, address) VALUES(#{username}, #{birthday}, #{sex}, #{address})
    </insert>

    <delete id="deleteById" parameterType="int">
        DELETE FROM user WHERE id = #{id}
    </delete>

    <update id="updateUser" parameterType="cn.adoredu.mybatis.po.User">
        UPDATE user SET username=#{username}, birthday=#{birthday}, sex=#{sex}, address=#{address} WHERE id=#{id}
    </update>

</mapper>

接口:

package cn.adoredu.mybatis.mapper;

import cn.adoredu.mybatis.po.User;

import java.util.List;

public interface UserMapper {

    User findUserById(int id) throws Exception;

    List<User> findUserByName(String username) throws Exception;

    void insertUser(User user) throws Exception;

    void deleteById(int id) throws Exception;

    void updateUser(User user) throws Exception;
}

测试类:

package cn.adoredu.myBatis;

import cn.adoredu.mybatis.mapper.UserMapper;
import cn.adoredu.mybatis.po.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;

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

public class UserMapperTest {

    private SqlSessionFactory sqlSessionFactory;

    @Before
    public void init() throws IOException {
        String resource = "SqlMapConfig.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }

    @Test
    public void testOne() throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 创建代理对象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        // 调用代理对象的方法
        User user = userMapper.findUserById(1);
        sqlSession.close();
        System.out.println(user);
    }

    @Test
    public void testDelete() throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        userMapper.deleteById(27);
        sqlSession.commit();
        sqlSession.close();
    }
}

SqlMapConfig.xml

Mybatis全局配置文件中支持配置的内容和顺序为:

  • properties:属性
  • settings:全局配置
  • typeAliases:类型别名
  • typeHandlers:类型处理器
  • objectFactory:对象工厂
  • plugins:插件
  • enviromments:环境集合属性对象
    • environment:环境
      • transactionManager:事务管理
      • dataSource:数据源
  • mappers:映射文件
  1. properties:

Properties里面可以定义属性,也可以指定properties文件。顺序上,定义的属性首先被读取,然后是引入的文件,最后是parameterType传递的属性,如果重名则后面覆盖前面。一般使用resource或url引入的properties文件而不使用内部定义属性。常见的配置如数据源信息:

<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <properties resource="db.properties" />

    <!-- 和spring整合后 environments配置将废除-->
    <environments default="development">
        <environment id="development">
            <!-- 使用jdbc事务管理-->
            <transactionManager type="JDBC" />
            <!-- 数据库连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 加载mapper.xml -->
    <mappers>
        <package name="cn.adoredu.mybatis.mapper"/>
    </mappers>

</configuration>

  1. settings:

全局配置参数,可以设置最大线程数,最大等待时间等。

  1. typeAliases:

可以将parameterType、resultType中指定的类型通过别名使用。例如:

<typeAliases>
    <!-- 定义单个别名-->
    <typeAlias type="cn.adoredu.mybatis.po.User" alias="user" />
    <!-- 批量定义别名,指定包路径,自动扫描包下面的pojo,别名默认为类名(大小写首字母均可)-->
    <package name="cn.adoredu.mybatis.po"/>
</typeAliases>
  1. typeHandlers:

将java类型和jdbc类型进行映射(mybatis提供很多默认映射)。

  1. mappers:

mappers配置映射文件除使用resource外,还可以使用class属性或者package标签配置:

<mappers>
	<!-- <mapper resource="mapper/UserMapper.xml" />-->
    <!-- <mapper class="cn.adoredu.mybatis.mapper.UserMapper" /> -->
    <!-- 通过package会自动扫描包下面的mapper接口和配置文件 -->
    <package name="cn.adoredu.mybatis.mapper" />
</mappers>

注意,若使用calss属性或package标签,则要求xml和接口同名且在同一目录下。另外如果使用maven构建项目,需要在pom文件中添加:

<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
        </resource>
    </resources>
</build>

ResultType和ResultMap

resultType:指定输出结果类型,将sql查询结果映射到Java对象。映射pojo时要注意查询sql列名和对象属性名要一致,否则无法映射,值为null;

resultMap:通过列名配置可以将查询名不一致的对象映射为Java对象;

映射配置:

<?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="cn.adoredu.mybatis.mapper.UserMapper">

    <!--
        id:resultMap的文艺标识
        type:要映射的pojo(全局配置文件中已定义别名)
     -->
    <resultMap id="userListResultMap" type="user">
        <!--
            id:pojo的主键
            column:结果集列名
            property:pojo属性名
        -->
        <id column="_id" property="id"></id>
        <!--
            result:普通列的映射配置
        -->
        <result column="_username" property="username"></result>
        <result column="_birthday" property="birthday"></result>
        <result column="_sex" property="_sex"></result>
        <result column="_address" property="address"></result>
    </resultMap>

    <!-- 映射配置和使用在同一xml中,resultMap属性直接使用id。否则为namespace.id -->
    <select id="findUserByResultMap" parameterType="String" resultMap="userListResultMap">
        SELECT * FROM user WHERE username like '%${value}%'
    </select>
    
    <!-- ... -->

</mapper>

动态sql

入门案例中,sql语句比较固定,不够灵活。如查询列表接口只能模糊查询username,而无法做到根据参数灵活查询。

  1. if-where:
<select id="findUserList" parameterType="user" resultType="user">
    SELECT * FROM user
    <where>
        <if test="username != null and username != ''">
            username like '%${username}'
        </if>
        <if test="sex != null and sex != ''">
            sex = #{sex}
        </if>
    </where>
</select>

通过配置log4j查看sql语句可以看出,这种方式可以比较灵活的实现根据条件查询。

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.5</version>
</dependency>

log4j.properties:

# Global logging configuration\uff0c\u5efa\u8bae\u5f00\u53d1\u73af\u5883\u4e2d\u8981\u7528debug
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
  1. sql片段:

上述方式比较灵活的实现了多条件查询。实际上,可以把条件片段抽取分离,这样比较公共的片段可以用于多个sql语句。

<?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="cn.adoredu.mybatis.mapper.UserMapper">

    <sql id="query_user_where">
        <if test="username != null and username != ''">
            AND username like '%${username}'
        </if>
        <if test="sex != null and sex != ''">
            AND sex = #{sex}
        </if>
    </sql>

    <select id="findUserList" parameterType="user" resultType="user">
        SELECT * FROM user
        <where>
            <!-- 同xml内,直接使用id引入。跨文件时,使用namespace.id -->
            <include refid="query_user_where"/>
            <!-- 下面可以继续跟其他类的sql片段作为条件 -->
        </where>
    </select>

</mapper>
  1. foreach

加入现查询条件为id在某范围内时,可以使用foreach实现。

首先要定义一个QueryVo类,包含ids属性:

package cn.adoredu.mybatis.po;

import java.util.List;

public class UserQueryVo {

    private User user;

    private List<String> ids;

    public User getUser() {
        return user;
    }

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

    public List<String> getIds() {
        return ids;
    }

    public void setIds(List<String> ids) {
        this.ids = ids;
    }
}
<?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="cn.adoredu.mybatis.mapper.UserMapper">

    <sql id="query_user_where">
        <if test="user!=null">
            <if test="user.username != null and user.username != ''">
                AND username like '%${user.username}'
            </if>
            <if test="user.sex != null and user.sex != ''">
                AND sex = #{user.sex}
            </if>
        </if>
        <!--
            collection:集合的属性;
            open:开始拼接字符串;
            close:结束拼接字符串;
            item:每次循环到的对象;
            separator:分隔字符串;
        -->
        <!-- and id in (1,2,3) -->
        <foreach collection="ids" open="AND id IN (" close=")" separator="," item="id">
            #{id}
        </foreach>
        <!--and (id=1 or id=2 or id=3)-->
        <!--<foreach collection="ids" open="and (" close=")" separator="OR" item="id">
            id=#{id}
        </foreach>-->
    </sql>

    <select id="findUserList" parameterType="userQueryVo" resultType="user">
        SELECT * FROM user
        <where>
            <include refid="query_user_where"/>
        </where>
    </select>

</mapper>

高级映射

表:用户表、订单表、订单明细表、商品表(参考:https://github.com/AdoredU/mybatis-demo)。

一对一查询
  1. resultType实现一对一查询

需求:查询订单,关联查询用户。

分析:因为resultType要查询字段名和属性名一致,因此创建结果po类,po类可以继承查询属性多的类简化。

package cn.adoredu.mybatis.po;

public class OrderCustom extends Orders {

    private String username;

    private String sex;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "OrderCustom{" +
                "username='" + username + '\'' +
                ", sex='" + sex + '\'' +
                '}';
    }
}

创建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="cn.adoredu.mybatis.mapper.OrderMapperCustom">
    <select id="findOrderUserList" resultType="orderCustom">
        SELECT orders.*, user.username, user.sex FROM orders, user WHERE orders.user_id = user.id
    </select>

</mapper>
  1. resultMap实现一对一查询

resultMap支持将一对一关联查询的记录映射到pojo中。首先在Orders类中添加User属性:

package cn.adoredu.mybatis.po;

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

public class Orders implements Serializable {
    private Integer id;

    private Integer userId;

    private String number;

    private Date createtime;

    private String note;

    // 因为是一对一,所以这里直接定义单个User而不是List<User>
    private User user;

    public Integer getId() {
        return id;
    }

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

    public Integer getUserId() {
        return userId;
    }

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

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number == null ? null : number.trim();
    }

    public Date getCreatetime() {
        return createtime;
    }

    public void setCreatetime(Date createtime) {
        this.createtime = createtime;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note == null ? null : note.trim();
    }

    public User getUser() {
        return user;
    }

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

映射配置:

<?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="cn.adoredu.mybatis.mapper.OrderMapperCustom">

    <resultMap id="ordersUserResultMap" type="orders">
        <id column="id" property="id" />
        <result column="user_id" property="userId" />
        <result column="number" property="number" />
        <result column="createtime" property="createtime" />
        <result column="note" property="note" />

        <!-- 使用association完成关联信息查询 -->
        <!-- property:属性名, javaType:java类型 -->
        <association property="user" javaType="user">
            <!-- id:关联信息的唯一标识 -->
            <id column="user_id" property="id" />
            <result column="username" property="username" />
            <result column="sex" property="sex" />
        </association>

    </resultMap>

    <select id="findOrderUserList" resultType="orderCustom">
        SELECT orders.*, user.username, user.sex FROM orders, user WHERE orders.user_id = user.id
    </select>

    <select id="findOrderUserResultMap" resultMap="ordersUserResultMap">
        SELECT orders.*, user.username, user.sex FROM orders, user WHERE orders.user_id = user.id
    </select>

</mapper>
一对多查询(resultMap实现)

需求:查询订单信息关联查询订单明细。

使用resultMap实现一对多要在一类中创建多类的集合:

package cn.adoredu.mybatis.po;

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

public class Orders implements Serializable {
    private Integer id;

    private Integer userId;

    private String number;

    private Date createtime;

    private String note;

    private User user;

    private List<Orderdetail> orderDetailList;
    
    // ...getters/setters
}

配置(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="cn.adoredu.mybatis.mapper.OrderMapperCustom">

    <resultMap id="ordersUserResultMap" type="orders">
        <id column="id" property="id" />
        <result column="user_id" property="userId" />
        <result column="number" property="number" />
        <result column="createtime" property="createtime" />
        <result column="note" property="note" />

        <!-- 使用association完成关联信息查询 -->
        <!-- property:属性名, javaType:java类型 -->
        <association property="user" javaType="user">
            <!-- id:关联信息的唯一标识 -->
            <id column="user_id" property="id" />
            <result column="username" property="username" />
            <result column="sex" property="sex" />
        </association>

    </resultMap>

    <!-- 使用继承简化order和user部分的写法 -->
    <resultMap id="ordersAndOrderDetails" type="orders" extends="ordersUserResultMap">
        <!-- 集合时使用oftype属性,值为pojo类型 -->
        <collection property="orderDetailList" ofType="orderDetail">
            <!-- property:oftype的pojo的属性 -->
            <id column="orderdetail_id" property="id" />
            <result column="items_num" property="itemsNum" />
            <result column="items_id" property="itemsId" />
            <result />
        </collection>
    </resultMap>

    <select id="findOrderUserList" resultType="orderCustom">
        SELECT orders.*, user.username, user.sex FROM orders, user WHERE orders.user_id = user.id
    </select>

    <select id="findOrderUserResultMap" resultMap="ordersUserResultMap">
        SELECT orders.*, user.username, user.sex FROM orders, user WHERE orders.user_id = user.id
    </select>

    <select id="findOrdersOrderDetailMap" resultMap="ordersAndOrderDetails">
        SELECT
            orders.*, USER .username,
            USER .sex,
            orderdetail.id orderdetail_id,
            orderdetail.items_id,
            orderdetail.items_num
        FROM
            orders,
            USER,
            orderdetail
        WHERE
            orders.user_id = USER .id
        AND orders.id = orderdetail.orders_id
    </select>

</mapper>

注意:如果直接执行上面一对多sql语句,会返回主查询对象重复的结果。而使用mybatis一对多resultMap配置后,一的对象是不重复的,而多的对象则作为一对象的集合。

延时加载

需求:查询订单关联查询用户信息。

要使用延时加载,首先要在mybatis全局配置文件中开启延时加载:

...
<settings>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>
...

注意需要cglib包:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.5</version>
</dependency>

配置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="cn.adoredu.mybatis.mapper.OrderMapperCustom">

    <resultMap id="ordersUserResultMap" type="orders">
        <id column="id" property="id" />
        <result column="user_id" property="userId" />
        <result column="number" property="number" />
        <result column="createtime" property="createtime" />
        <result column="note" property="note" />

        <!-- 使用association完成关联信息查询 -->
        <!-- property:属性名, javaType:java类型 -->
        <association property="user" javaType="user">
            <!-- id:关联信息的唯一标识 -->
            <id column="user_id" property="id" />
            <result column="username" property="username" />
            <result column="sex" property="sex" />
        </association>

    </resultMap>

    <!-- 使用继承简化order和user部分的写法 -->
    <resultMap id="ordersAndOrderDetails" type="orders" extends="ordersUserResultMap">
        <!-- 集合时使用oftype属性,值为pojo类型 -->
        <collection property="orderDetailList" ofType="orderDetail">
            <!-- property:oftype的pojo的属性 -->
            <id column="orderdetail_id" property="id" />
            <result column="items_num" property="itemsNum" />
            <result column="items_id" property="itemsId" />
        </collection>
    </resultMap>

    <!-- 一对一延时加载配置 -->
    <!-- 用户信息仍是orders的一个属性,查的还是orders,type为orders -->
    <resultMap id="orderCustomLazyLoading" type="orders">
        <id column="id" property="id"/>
        <result column="user_id" property="userId"/>
        <result column="number" property="number"/>
        <result column="createtime" property="createtime"/>
        <result column="note" property="note"/>
        <association property="user" select="cn.adoredu.mybatis.mapper.UserMapper.findUserById" column="user_id" />
    </resultMap>

    <select id="findOrderUserList" resultType="orderCustom">
        SELECT orders.*, user.username, user.sex FROM orders, user WHERE orders.user_id = user.id
    </select>

    <select id="findOrderUserResultMap" resultMap="ordersUserResultMap">
        SELECT orders.*, user.username, user.sex FROM orders, user WHERE orders.user_id = user.id
    </select>

    <select id="findOrdersOrderDetailMap" resultMap="ordersAndOrderDetails">
        SELECT
            orders.*, USER .username,
            USER .sex,
            orderdetail.id orderdetail_id,
            orderdetail.items_id,
            orderdetail.items_num
        FROM
            orders,
            USER,
            orderdetail
        WHERE
            orders.user_id = USER .id
        AND orders.id = orderdetail.orders_id
    </select>
    
    <select id="findOrdersUserLazyLoading" resultMap="orderCustomLazyLoading">
        SELECT * FROM orders
    </select>

</mapper>

此时在查询时只会执行查询订单语句,只有当使用到订单信息中用户时才会执行查询用户操作。

实际上,mybatis的延时加载无法作用到页面端,因为它只在同一会话中有效,即可以理解为它只作用至service曾。因此实际项目中使用并不多,具体看需求。

Mybatis缓存

Mybatis缓存可以分为一级缓存和二级缓存两种。

一级缓存:sqlSession级别的,即在同一个sqlSession内有效。当首次执行查询时,会从数据库查询数据,并把结果缓存起来,再次查询时不会向数据库发送请求而是直接从缓存中查询。但执行commit操作(增、删、改)时,会把缓存区清空。

Mybatis默认支持一级缓存,无需配置。但需要注意的是,当和spring整合使用时,不支持一级缓存,这是因为spring按照模板生成mapper代理对象,会统一执行关闭sqlSession。

二级缓存:跨sqlSession,是mapper级别的,同一个命名空间下有效,mapper以命名空间为单位创建缓存Map。二级缓存需要配置:

  1. 在全局配置文件中settings下配置开启缓存:<setting name="cacheEnabled" value="true" />

  2. 映射文件中配置cache:<cache />
  3. 缓存的pojo类序列化(pojo中若有类属性,也要序列化);

注意:

  • 通过跟踪代码(sqlSession的select方法)不难发现,mybatis优先查询二级缓存;

  • 禁用二级缓存:若对部分sql禁用二级缓存,则在statement中添加属性userCache="false"
  • 刷新缓存:在增删改的sql中,有默认属性flushCache="true",表示执行时会清空二级缓存,当然可以手动修改为false表示不清空(一般不会有这类需求);
  • 映射文件中<cache />常用属性有flushInterval="60000",单位毫秒;

Mybatis和Spring整合

思路:

  • spring管理数据源;
  • spring管理SqlSessionFactory;
  • spring管理mapper/dao对象(包括事务等);
  1. 添加依赖:mybatis、spring、mysql、mybatis-spring、dbcp
<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>

    <groupId>cn.adoredu</groupId>
    <artifactId>mybatis-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <spring.version>4.2.4.RELEASE</spring.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.2.2</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/commons-dbcp/commons-dbcp -->
        <dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>1.2.2</version>
        </dependency>


        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.2.7</version>
        </dependency>

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

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.5</version>
        </dependency>

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.5</version>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
    </build>

</project>
  1. 配置sqlMapConfig.xml:
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>


    <typeAliases>
        <package name="cn.adoredu.mybatis.po"/>
    </typeAliases>

    <!-- 加载mapper.xml -->
    <mappers>
        <!-- 用于原生dao开发 -->
        <mapper resource="sqlmap/User.xml" />

        <package name="cn.adoredu.mybatis.mapper"/>
    </mappers>

</configuration>
  1. 配置applicationContext.xml:
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context-3.2.xsd
		http://www.springframework.org/schema/aop
		http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
		http://www.springframework.org/schema/tx
		http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">

    <!-- 加载配置文件 -->
    <context:property-placeholder location="classpath:db.properties" />

    <!-- 数据源 -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driver}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="maxActive" value="10" />
        <property name="maxIdle" value="5" />
    </bean>

    <!-- 配置sqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!-- mybatis配置文件 -->
        <property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml" />
    </bean>

    <!-- mybatis整合spring共三种方式开发 -->
    <!-- 方式一:原生dao方式,要配置dao -->
    <bean id="userDao" class="cn.adoredu.mybatis.dao.UserDaoImpl">
        <property name="sqlSessionFactory" ref="sqlSessionFactory" />
    </bean>

    <!-- 方式二:使用MapperFactoryBean
        MapperFactoryBean:用于生成代理对象。该类本身继承自SqlSessionDaoSupport
        此方法每个mapper都要配置
     -->
    <!--<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
        <property name="mapperInterface" value="cn.adoredu.mybatis.mapper.UserMapper" />
        <property name="sqlSessionFactory" ref="sqlSessionFactory" />
     </bean>-->

    <!-- 方式三:使用MapperScannerConfigurer
        MapperScannerConfigurer:将包下面mapper自动创建代理对象到spring容器中,id为mapper类型首字母小写
     -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 扫描多个包,逗号拼接 -->
        <property name="basePackage" value="cn.adoredu.mybatis.mapper" />
        <!-- 注意,这里使用的是sqlSessionFactoryBeanName,后面为value。这是为避免版本遗留问题导致冲突 -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    </bean>

</beans>

测试类:

package cn.adoredu.myBatis;

import cn.adoredu.mybatis.dao.UserDao;
import cn.adoredu.mybatis.mapper.UserMapper;
import cn.adoredu.mybatis.po.User;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class UserSpringTest {

    private ApplicationContext context;

    @Before
    public void init() throws Exception {
        context = new ClassPathXmlApplicationContext("spring/applicationContext.xml");
    }

    @Test
    public void testDao() throws Exception {
        UserDao userDao = (UserDao) context.getBean("userDao");
        User user = userDao.findUserById(1);
        System.out.println(user);
    }

    @Test
    public void testMapper() throws Exception {
        UserMapper userMapper = (UserMapper) context.getBean("userMapper");
        User user = userMapper.findUserById(1);
        System.out.println(user);
    }

}