MyBatis框架

MyBatis框架

1 介绍

MyBatis 是一个开源的持久层框架,它简化了数据库交互的过程。与许多其他持久层框架不同,MyBatis 不会强制你使用对象关系映射(ORM)的范式。它采用了一种将 Java 对象和数据库记录进行映射的灵活方式,可以通过 XML 或者注解配置 SQL 映射关系,使得开发者可以自由地定制 SQL,而不需要像 Hibernate 那样需要遵循特定的对象模型。

以下是 MyBatis 的一些主要特点和优势:

  1. 简单易学: MyBatis 不需要学习复杂的配置,简单易上手。

  2. 灵活性: MyBatis 允许使用原生 SQL、存储过程和高级映射。

  3. 动态 SQL: MyBatis 提供了强大的动态 SQL 支持,可以根据不同的条件生成不同的 SQL。

  4. 自动映射: MyBatis 提供了自动将查询结果映射到 Java 对象的功能,无需手动设置映射规则。

  5. 与 Spring 和其他框架集成: MyBatis 易于与 Spring 等框架集成,可以方便地用于各种 Java 项目。

  6. 缓存支持: MyBatis 提供了一级缓存和二级缓存的支持,可以有效地提高查询性能。

  7. 可插拔的: MyBatis 的设计允许开发者编写自定义的插件来扩展框架功能。

  8. 不需要使用 DAO 接口: 在 MyBatis 中,可以直接使用映射文件中配置的 SQL,而不需要创建繁琐的 DAO 接口。

2 JDBC局限性

JDBC(Java Database Connectivity)是 Java 中用于与数据库进行交互的标准接口。尽管 JDBC 是一个强大的工具,但它也有一些局限性:

  1. 冗长的代码: 使用 JDBC 进行数据库操作需要编写很多冗长的代码,包括数据库连接的建立和关闭、异常处理、SQL 语句的构建等。这些代码量较大,容易出错,降低了开发效率。

  2. 手动处理异常: 在 JDBC 中,开发者需要手动处理 SQL 异常,这意味着在每次数据库操作时都需要编写大量的异常处理代码,增加了代码的复杂性。

  3. 硬编码的 SQL 语句: 在 JDBC 中,SQL 语句通常是硬编码在 Java 代码中的,这样的做法不利于代码的维护和修改。如果需要修改 SQL 语句,开发者需要修改 Java 代码,这样的耦合性较高。

  4. 性能: JDBC 的性能通常较低,特别是在频繁进行数据库操作的时候。每次数据库操作都需要建立连接、执行 SQL 语句、关闭连接,这些操作会增加数据库的负担。

  5. 缺乏对象关系映射(ORM): JDBC 是一种底层的数据库操作接口,它不提供对象关系映射的功能。这意味着开发者需要手动将数据库查询结果映射到 Java 对象,增加了开发的复杂性。

  6. 事务管理: 在 JDBC 中,事务管理需要手动编写代码来处理,包括事务的开始、提交、回滚等操作。这些操作容易出错,并且增加了代码的复杂性。

为了解决这些问题,很多 Java 开发者转向了使用持久层框架,如Hibernate、MyBatis 等。这些框架提供了更高级、更方便的数据库操作方式,能够简化开发流程,提高开发效率。使用这些框架,开发者可以更专注于业务逻辑的实现,而不是花费大量的时间在处理数据库操作上。

3 MyBatis框架底层

MyBatis是一个基于Java的持久化框架,它的底层原理主要围绕着数据库访问和SQL映射展开。以下是MyBatis框架底层原理的主要组成部分:

  1. 配置文件(mybatis-config.xml):
    MyBatis的配置文件包含了框架的核心配置信息,包括数据源、事务管理、缓存配置等。它定义了框架的全局行为。

  2. 映射文件(Mapper.xml):
    映射文件是MyBatis的核心组成部分,它包含了SQL语句的定义以及这些SQL语句与Java方法之间的映射关系。在映射文件中,可以定义SQL查询、插入、更新、删除等操作,以及它们的输入参数和输出结果的映射。

  3. SqlSessionFactory:
    SqlSessionFactory 是 MyBatis 的核心接口,它负责创建 SqlSession 实例。SqlSessionFactory 的实现类 DefaultSqlSessionFactory 负责解析配置文件,构建并管理 SqlSession 的生命周期。

  4. SqlSession:
    SqlSession 是 MyBatis 框架中用于执行 SQL 操作的主要接口。它提供了各种查询、插入、更新、删除等方法,以及提交事务、关闭资源等功能。

  5. Executor:
    Executor 负责 SQL 的执行,它会将映射文件中定义的 SQL 语句解析成数据库可以执行的语句,执行SQL 并返回结果。

  6. StatementHandler、ParameterHandler、ResultSetHandler、TypeHandler:
    这些组件负责具体的 SQL 语句处理,比如 StatementHandler 负责 PreparedStatement 的创建和参数设置,ParameterHandler 负责 SQL 参数的处理,ResultSetHandler 负责结果集的处理,TypeHandler 负责 Java 类型与数据库类型之间的转换。

在 MyBatis 的工作流程中,SqlSessionFactory 负责初始化并加载配置文件,创建 SqlSession 实例。SqlSession 通过映射文件执行 SQL 语句,调用 Executor 执行具体的数据库操作。执行结果由ResultSetHandler 处理,最终返回给调用者。

MyBatis 的工作流程图:

在这里插入图片描述

4 入门案例

4.1 全局配置文件 SqlMapConfig.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <!-- 打印sql日志 -->
        <setting name="logImpl" value="STDOUT_LOGGING" />
    </settings>
    
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql:///mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="kdx010908"/>
            </dataSource>
        </environment>
    </environments>
    
    <mappers>
        <mapper resource="com/kdx/entity/User.xml"/>
        <mapper resource="com/kdx/mapper/UserMapper.xml"/>
    </mappers>
</configuration>

4.2 实体类User

User.java

public class User {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    public User() {
    }

    public User(String username) {
        this.username = username;
    }

    public Integer getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

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

    public Date getBirthday() {
        return birthday;
    }

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

    public String getSex() {
        return sex;
    }

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

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

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

4.3 User.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="test">
    <select id="findUserById" resultType="com.kdx.entity.User" parameterType="int">
        select * from user where id = #{id}
    </select>

    <insert id="addUser" parameterType="com.kdx.entity.User">
        INSERT INTO USER (username) VALUES (#{username})
    </insert>

    <update id="updateUser" parameterType="int">
        UPDATE USER SET username = #{username}  WHERE id = #{id}
    </update>

    <delete id="deleteUser" parameterType="int">
        DELETE FROM USER WHERE id = #{id}
    </delete>
</mapper>

4.4 测试

UserTest .java

public class UserTest {

    @Test
    public void findUserById() throws Exception{
        String resource = "SqlMapConfig.xml";
        //读配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream(resource);
        //创建会话工厂
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        //打开会话
        SqlSession sqlSession = sqlSessionFactory.openSession();
        User user = sqlSession.selectOne("test.findUserById",1);
        System.out.println(user);
        //关闭会话
        sqlSession.close();
    }

    @Test
    public void addUser() throws Exception{
        String resource = "SqlMapConfig.xml";
        InputStream resourceAsStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        sqlSession.insert("test.addUser",new User("kdx"));
        sqlSession.commit();
        System.out.println("ok");
        sqlSession.close();
    }

    @Test
    public void updateUser() throws Exception{
        String resource = "SqlMapConfig.xml";
        InputStream resourceAsStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        sqlSession.update("test.updateUser",16);
        sqlSession.commit();
        System.out.println("ok");
        sqlSession.close();
    }

    @Test
    public void deleteUser() throws Exception{
        String resource = "SqlMapConfig.xml";
        InputStream resourceAsStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        sqlSession.delete("test.deleteUser",37);
        sqlSession.commit();
        System.out.println("ok");
        sqlSession.close();
    }
}

5 Dao开发模式

5.1 UserDao接口

public interface UserDao {

    //根据id查询用户
    public User findUserById(Integer id)throws  Exception;
}

5.2 UserDaoImpl

public class UserDaoImpl implements UserDao {

    private SqlSessionFactory sqlSessionFactory;

    public UserDaoImpl(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }

    @Override
    public User findUserById(Integer id) throws Exception {
        //会话
        SqlSession sqlSession = sqlSessionFactory.openSession();
        User user=sqlSession.selectOne("test.findUserById",id);
        sqlSession.close();
        return user;
    }
}

5.3 UserDaoTest

public class UserDaoTest {
    SqlSessionFactory factory =null;
    @Before
    public void beforeMethod() throws IOException {
        String resource="SqlMapConfig.xml";
        //解析
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 构建 sqlSessionFactory
        factory = new SqlSessionFactoryBuilder().build(inputStream);
    }

    @Test
    public void testFind() throws Exception {
        //创建 UserDao的对象
        UserDao userDao = new UserDaoImpl(factory);
        User user = userDao.findUserById(1);
        System.out.println(user);
    }
}

6 mapper代理模式的开发

6.1 规范

1.在 mapper.xml 中 namespace 为 mapper 接口地址

<mapper namespace="com.kdx.mapper.UserMapper"></mapper>

2.在 mapper.xml 中 statementID 要和 mapper 接口中的方法名保持一致
例如:UserMapper .xml中的

<select id="findAll" resultType="com.kdx.entity.User">
    select id,username,birthday,sex,address from user
</select>

对应 UserMapper 接口中的

public List<User> findAll();

3.在 mapper.xml 中的 resultType 要和 mapper.java 中输出参数类型保持一致[返回值]
例如上面例子中 resultType 和 User 对应

4.在 mapper.xml 中的 parameterType 要和 mapper.java 中输入参数类型保持一致[形参]
例如:UserMapper .xml中的 parameterType

<delete id="delByUserId" parameterType="int">
    DELETE FROM USER WHERE id = #{id}
</delete>

对应 UserMapper 接口中的 int

public void delByUserId(int id);

6.2 案例

6.2.1 UserMapper.xml

<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kdx.mapper.UserMapper">
    <resultMap id="UserResultMap" type="com.kdx.entity.User">
        <id column="user_id" property="id"/>
        <result column="username" property="username"/>
        <result column="birthday" property="birthday"/>
        <result column="sex" property="sex"/>
        <result column="address" property="address"/>
    </resultMap>

    <select id="findAll" resultMap="UserResultMap">
        select id user_id,username,birthday,sex,address from user
    </select>

    <insert id="addUser">
        INSERT INTO USER (username) VALUES (#{username})
    </insert>

    <update id="updateUser">
        update USER
        <set>
            <if test="username != null and username != ''">
                username = #{username}
            </if>
        </set>
        where id = #{id}
    </update>

    <delete id="delByUserId" parameterType="int">
        DELETE FROM USER WHERE id = #{id}
    </delete>
</mapper>

6.2.2 UserMapper接口

public interface UserMapper {

    List<User> findAll();

    void addUser(User user);

    void updateUser(User user);

    void delByUserId(Integer id);
}

6.2.3 UserMapperTest 测试

public class UserMapperTest {
    
    private SqlSessionFactory factory = null;
    private SqlSession sqlSession = null;
    
    @Before
    public void before() throws IOException {
        String resource = "SqlMapConfig.xml";
        InputStream resourceAsStream = Resources.getResourceAsStream(resource);
        factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        sqlSession = factory.openSession();
    }
    
    @Test
    public void findAll() {
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = userMapper.findAll();
        for (User user : userList) {
            System.out.println(user);
        }
    }

    @Test
    public void addUser() {
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        userMapper.addUser(new User("hhh"));
        sqlSession.commit();
        System.out.println("ok");
        sqlSession.close();
    }

    @Test
    public void updateUser(){
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = new User();
        user.setUsername("kdx");
        user.setId(36);
        userMapper.updateUser(user);
        sqlSession.commit();
        System.out.println("ok");
        sqlSession.close();
    }

    @Test
    public void delByUserId(){
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        userMapper.delByUserId(40);
        sqlSession.commit();
        System.out.println("ok");
        sqlSession.close();
    }
}

7 动态 SQL

MyBatis框架提供了一种称为动态SQL的功能,允许根据不同的条件动态构建SQL语句。这在实际应用中非常有用,因为它可以帮助你避免编写大量重复的SQL语句,提高了SQL语句的可维护性和灵活性。

参考网址:https://mybatis.net.cn/dynamic-sql.html

以下是MyBatis中动态SQL的两种常见用法:
1.if 元素

<select id="findUserList" parameterType="com.kdx.vo.UserQueryVo" resultType="com.kdx.entity.UserCustom">
        select * from user
        <where>
            <if test="userCustom != null">
                <if test="userCustom.username != null and userCustom.username != ''">
                    and user.username like '%${userCustom.username}%'
                </if>

                <if test="userCustom.sex != null and userCustom.sex != ''">
                    and user.sex = #{userCustom.sex}
                </if>
            </if>
        </where>
</select>

以上示例中,在查询用户时,若传入用户的名字则在 sql 语句中拼接 and user.username like '%${userCustom.username}%',若传入用户的性别则在 sql 语句中拼接 and user.sex = #{userCustom.sex}

<where>标签相当于 sql 语句中的 where 1=1

2.foreach 元素:对集合进行遍历(尤其是在构建 IN 条件语句的时候)
<foreach>标签中各个属性解释如下:

  • collection : 输入对象中属性的集合
  • item 每个遍历生成的对象
  • open :开始遍历时拼接的串
  • close : 遍历结束时拼接的串
  • separator : 两个对象中拼接需要的串

例如查询 id 为1,10,15用户的信息,sql语句有两种写法
若为:

SELECT * FROM USER WHERE id = 1 OR id = 10 OR id =15

xml 文件如下

<foreach collection="ids" item="user_id" open="and (" close=")" separator="or">
     id = #{user_id}
</foreach>

若为:

SELECT * FROM USER WHERE id IN (1,10,15)

xml 文件如下

<foreach collection="ids" item="user_id" open="and id in(" close=")" separator=",">
     #{user_id}
</foreach>

8 SQL 片段

SQL 片段是 MyBatis 中的一种重用 SQL代码 的方法。它能够将一部分 SQL 代码定义成一个可重用的片段,然后在需要的地方进行引用,避免了代码的重复。SQL 片段通常定义在 MyBatis 的XML配置文件中,可以通过<sql>标签来创建。

例如:

<sql id="findUserNameSex">
     <if test="userCustom != null">
          <if test="userCustom.username != null and userCustom.username != ''">
              and user.username like '%${userCustom.username}%'
          </if>

          <if test="userCustom.sex != null and userCustom.sex != ''">
              and user.sex = #{userCustom.sex}
          </if>
      </if>
</sql>

然后,在其他地方的 SQL 语句中,可以通过<include>标签来引用这个 SQL 片段:

<select id="findUserList" parameterType="com.kdx.vo.UserQueryVo" resultType="com.kdx.entity.UserCustom">
      select * from user
      <where>
          <include refid="findUserNameSex"/>
      </where>
</select>

9 面试题:${} 和 #{}区别

#{}是预编译处理, ${}是字符串替换。

Mybatis 在处理#{}时,会将 sql 中的#{}替换为?号,调用 PreparedStatement 的 set 方法来赋值;

Mybatis 在处理${}时,就是把${}替换成变量的值。这样可能会存在SQL注入的风险。因为参数值会被直接拼接到SQL语句中,不会被预编译,可能导致安全问题。而使用#{}可以有效的防止 SQL 注入,提高系统安全性。

10 关联查询

例如有四张表,user、orders、orderdetail、items,它们的关系如下:
user --> orders:一个用户可以创建多个订单,一对多

orders --> user: 一个订单只能由一个用户创建,一对一

orders -->orderdetail:一个订单可以有多个订单明细,一对多

orderdetail --> orders:一个订单明细只能属于一个订单,一对一

orderdetail --> items:一个订单明细中只能有一个商品,一对一

items --> orderdetail: 一个商品可以出现在多个订单明细中,一对多

10.1 一对一

需求: 查询订单,关联查询创建订单的用户信息
思路:
确定查询主表:订单表
确定查询关联表: 用户表
使用内连接
确定SQL:

SELECT orders.*,user.`username`,user.`sex`,user.`address`
FROM USER,orders
WHERE user.`id`=orders.`user_id`

订单实体类 Orders:

public class Orders implements Serializable {
    private Integer id;
    private Integer userId;
    private String number;
    private Date createtime;
    private String note;
   
    private User user; 
    
    public Orders() {
    }

    public Orders(Integer userId, String number, Date createtime) {
        this.userId = userId;
        this.number = number;
        this.createtime = createtime;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.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;
    }

    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;
    }

    @Override
    public String toString() {
        return "Orders{" +
                "id=" + id +
                ", userId=" + userId +
                ", number='" + number + '\'' +
                ", createtime=" + createtime +
                ", note='" + note + '\'' +
                ", user=" + user +
                '}';
    }
}

使用定制类OrdersCustom接收:

public class OrdersCustom extends Orders{
    private String username;
    private String sex;
    private String address;

    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 String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

UserMapper接口:

public interface UserMapper {
 	//查询用户及订单
    //resultType
    List<OrdersCustom> findOrderAndUser();
    //resultMap
    List<OrdersCustom> findOrderAndUserResultMap();
}

UserMapper.xml
若用 resultType

<select id="findOrderAndUser" resultType="com.kdx.entity.OrdersCustom">
      SELECT orders.*,user.`username`,user.`sex`,user.`address`
      FROM USER,orders
      WHERE orders.`user_id`=user.`id`
</select>

若用 resultMap

<select id="findOrderAndUserResultMap" resultMap="findOrderAndUserMap">
      SELECT orders.*,user.`username`,user.`sex`,user.`address`
      FROM USER,orders
      WHERE orders.`user_id`=user.`id`
</select>

<resultMap id="findOrderAndUserMap" type="com.kdx.entity.OrdersCustom">
    <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" javaType="com.kdx.entity.User">
        <id column="user_id" property="id"/>
        <result column="username" property="username"/>
        <result column="sex" property="sex"/>
        <result column="address" property="address"/>
    </association>
</resultMap>

resultType 和 resultMap 总结
resultType 使用比较简单,如果pojo中的属性和数据库中的列名完全一致, 即可以使用它完成映射。没有特殊要求的查询使用 resultType 完成映射

resultMap 需要单独写 resultMap,有点儿小麻烦,如果对查询有特殊要求【比如添加user属性】 用resultMap 完成映射

resultMap可以实现延迟加载,resultType无法实现延迟加载。

10.2 一对多

需求:查询订单以及订单明细
思路:
确定查询主表:订单表
确定查询关联表: 订单明细表
使用内连接
确定SQL:

SELECT orders.*,
       user.`username`,user.`sex`,user.`address`,
       orderdetail.`items_id`,orderdetail.`items_num`,orderdetail.`orders_id`
FROM USER,orders,orderdetail
WHERE user.`id`=orders.`user_id` AND orders.`id`=orderdetail.`orders_id`

订单明细实体类 OrderDetail:

public class OrderDetail implements Serializable {
    private Integer id;
    private Integer ordersId;
    private Integer itemsId;
    private Integer itemsNum;
    
    public OrderDetail() {
    }

    public OrderDetail(Integer ordersId, Integer itemsId) {
        this.ordersId = ordersId;
        this.itemsId = itemsId;
    }

    public Integer getId() {
        return id;
    }

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

    public Integer getOrdersId() {
        return ordersId;
    }

    public void setOrdersId(Integer ordersId) {
        this.ordersId = ordersId;
    }

    public Integer getItemsId() {
        return itemsId;
    }

    public void setItemsId(Integer itemsId) {
        this.itemsId = itemsId;
    }

    public Integer getItemsNum() {
        return itemsNum;
    }

    public void setItemsNum(Integer itemsNum) {
        this.itemsNum = itemsNum;
    }

    @Override
    public String toString() {
        return "OrderDetail{" +
                "id=" + id +
                ", ordersId=" + ordersId +
                ", itemsId=" + itemsId +
                ", itemsNum=" + itemsNum +
                '}';
    }
}

在 用户实体类 User 中加入:

//订单
private List<Orders> ordersList = new ArrayList<>();

public List<Orders> getOrdersList() {
    return ordersList;
}

public void setOrdersList(List<Orders> ordersList) {
    this.ordersList = ordersList;
}

在 订单实体类 Orders 中加入:

//订单明细
private List<OrderDetail> orderdetails = new ArrayList<>();

public List<OrderDetail> getOrderDetailList() {
    return orderdetails;
}

public void setOrderDetailList(List<OrderDetail> orderdetails) {
    this.orderdetails = orderdetails;
}

在 UserMapper接口 加入:

//查询订单及订单明细 
List<OrderDetail> findOrderAndUserAndOrderDetail();

UserMapper.xml:

<select id="findOrderAndUserAndOrderDetail" resultMap="findOrderAndUserAndOrderDetailResultMap">
       SELECT orders.*,
       	      user.`username`,user.`sex`,user.`address`,
              orderdetail.`items_id`,orderdetail.`items_num`,orderdetail.`orders_id`
 	   FROM USER,orders,orderdetail
	   WHERE user.`id`=orders.`user_id` AND orders.`id`=orderdetail.`orders_id`
</select>

<!--继承了findOrderAndUserMap-->
<resultMap id="findOrderAndUserAndOrderDetailResultMap" type="com.kdx.entity.Orders" extends="findOrderAndUserMap"> 

    <collection property="orderdetails" ofType="com.kdx.entity.OrderDetail">
        <id column="orderdetail_id" property="id"/>
        <result column="orders_id" property="ordersId"/>
        <result column="items_id" property="itemsId"/>
        <result column="items_num" property="itemsNum"/>
    </collection>

</resultMap>

10.3 多对多

需求: 查询用户及用户购买的商品
思路:
确定查询主表: 用户表
确定查询关联表:由于用户和商品没有直接关联,所以通过订单表、订单明细表以及商品表进行关联
使用内连接
确定SQL:

SELECT orders.*,
       user.`username`,user.`sex`,user.`address`,orderdetail.`id` orderdetail_id,
       orderdetail.`items_id`,orderdetail.`items_num`,orderdetail.`orders_id`,
       items.`name` item_name,items.`price` item_price,items.`detail` item_detail
FROM USER,orders,orderdetail,items
WHERE user.`id`=orders.`user_id` AND orders.`id`=orderdetail.`orders_id` AND orderdetail.`items_id`=items.`id`

由于在一对多的例子中已经做了许多工作,在此不再重复
商品实体类 Items:

public class Items implements Serializable {
    private Integer id;
    private String name;
    private Double price;
    private String detail;
    private String pic;
    private Date createtime;

    public Items() {
    }

    public Items(Integer id, String name, Double price, String detail, String pic, Date createtime) {
        this.id = id;
        this.name = name;
        this.price = price;
        this.detail = detail;
        this.pic = pic;
        this.createtime = createtime;
    }

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    public String getDetail() {
        return detail;
    }

    public void setDetail(String detail) {
        this.detail = detail;
    }

    public String getPic() {
        return pic;
    }

    public void setPic(String pic) {
        this.pic = pic;
    }

    public Date getCreatetime() {
        return createtime;
    }

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

    @Override
    public String toString() {
        return "Items{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", price=" + price +
                ", detail='" + detail + '\'' +
                ", pic='" + pic + '\'' +
                ", createtime=" + createtime +
                '}';
    }
}

在订单明细实体类 OrderDetail 中加入:

//商品
private Items items;

public Items getItems() {
    return items;
}

public void setItems(Items items) {
    this.items = items;
}

在 UserMapper接口 加入:

//查询用户及其购买的商品
List<User> findUserAndItemResultMap();

UserMapper.xml:

<select id="findUserAndItemResultMap" resultMap="findUserItemResultMap">
     SELECT orders.*,
             user.`username`,user.`sex`,user.`address`,orderdetail.`id` orderdetail_id,
             orderdetail.`items_id`,orderdetail.`items_num`,orderdetail.`orders_id`,
             items.`name` item_name,items.`price` item_price,items.`detail` item_detail
      FROM USER,orders,orderdetail,items
      WHERE user.`id`=orders.`user_id` AND orders.`id`=orderdetail.`orders_id` AND orderdetail.`items_id`=items.`id`
</select>

    <resultMap id="findUserItemResultMap" type="com.kdx.entity.User">
        <id column="user_id" property="id"/>
        <result column="username" property="username"/>
        <result column="birthday" property="birthday"/>
        <result column="sex" property="sex"/>
        <result column="address" property="address"/>

        <collection property="ordersList" ofType="com.kdx.entity.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"/>

            <collection property="orderdetails" ofType="com.kdx.entity.OrderDetail">
                <id column="orderdetail_id" property="id"/>
                <result column="orders_id" property="ordersId"/>
                <result column="items_id" property="itemsId"/>
                <result column="items_num" property="itemsNum"/>

                <association property="items" javaType="com.kdx.entity.Items">
                    <id column="items_id" property="id"/>
                    <result column="item_name" property="name"/>
                    <result column="item_price" property="price"/>
                    <result column="item_detail" property="detail"/>
                </association>
            </collection>
        </collection>
    </resultMap>

11 延迟加载

MyBatis 中的延迟加载(Lazy Loading,又叫懒加载、慢加载)是一种性能优化技术,它可以在需要的时候才从数据库中加载数据,而不是在对象初始化时就立即加载。这样可以避免在查询主对象时同时加载关联对象,提高了查询性能和减少了数据库查询次数,也就是说可以按需查询数据库。

延迟加载只能使用 <resultMap> 来实现, <association>、<collection> 都具备延迟加载功能。

示例:查询订单表关联查询用户信息
1.全局配置文件 SqlMapConfig.xml 中加入以下内容

<settings>
       <!-- 延迟加载开关,当开启时,所有关联对象都会延迟加载。-->
       <setting name="lazyLoadingEnabled" value="true"/>
       <!-- 默认false(在 3.4.1 及之前的版本中默认为 true),这意味着在默认情况下,MyBatis 会使用懒加载,只有在需要使用延迟加载属性时,才会触发加载。-->
       <setting name="aggressiveLazyLoading" value="false"/>
</settings>

2.在UserMapper.xml 中加入以下内容

	<select id="findUserById" resultType="com.kdx.entity.User" parameterType="int">
        select * from user where id = #{id}
    </select>

    <select id="findOrdersAndUserLazyLoading" resultMap="lazyLoading">
        select * from orders
    </select>

    <resultMap id="lazyLoading" type="com.kdx.entity.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" javaType="com.kdx.entity.User" select="findUserById" column="user_id">

        </association>
    </resultMap>

3.在UserMapper 接口中加入以下内容

//根据id查询用户
User findUserById(int id);
//延迟加载查询订单及用户信息
List<Orders> findOrdersAndUserLazyLoading();

4.测试延迟加载

public class UserMapperTest {
	private SqlSessionFactory factory = null;
    private SqlSession sqlSession = null;
    
    //在JUnit测试中,@Before 注解标记的方法会在每个测试方法运行之前被执行。
    @Before
    public void before() throws IOException {
        String resource = "SqlMapConfig.xml";
        InputStream resourceAsStream = Resources.getResourceAsStream(resource);
        factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        sqlSession = factory.openSession();
    }
    
    //测试延迟加载
	@Test
    public void findOrdersAndUserLazyLoading(){
        sqlSession = factory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<Orders> ordersList = userMapper.findOrdersAndUserLazyLoading();

        for (int i = 0; i < ordersList.size(); i++) {
            System.out.println("------------------------------");
            Orders orders = ordersList.get(i);
            User user = orders.getUser();
        }
    }
}

在 fori 循环之前打断点进行调试:
在这里插入图片描述

12 缓存支持

MyBatis提供了两级缓存机制来提高性能:一级缓存(本地缓存)和二级缓存(全局缓存)

12.1 一级缓存(Local Cache / Session Cache)

一级缓存是指在同一个 SqlSession 中进行的缓存。默认情况下,MyBatis 会开启一级缓存。当执行一个查询时,查询的结果会被放入 SqlSession 的缓存中,如果后续的查询使用相同的 SqlSession 对象并且参数也相同,MyBatis 会直接从缓存中取得结果,而不会再次查询数据库。

一级缓存的生命周期是和 SqlSession 绑定的,当 SqlSession 关闭时,缓存数据也会被清空。

测试一级缓存:

public class UserMapperTest {
	private SqlSessionFactory factory = null;
    private SqlSession sqlSession = null;
    
    //在JUnit测试中,@Before 注解标记的方法会在每个测试方法运行之前被执行。
    @Before
    public void before() throws IOException {
        String resource = "SqlMapConfig.xml";
        InputStream resourceAsStream = Resources.getResourceAsStream(resource);
        factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        sqlSession = factory.openSession();
    }
    
    //测试一级缓存
	@Test
    public void testCache1(){
        sqlSession = factory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user1 = userMapper.findUserById(1);
        System.out.println("-------------------------");
        User user2 = userMapper.findUserById(1);
        System.out.println(user1==user2);
        sqlSession.close();
    }
}

在查询用户信息之前打断点进行调试,调试结果如下:
在这里插入图片描述
从调试结果来看,第一次查询用户信息时,通过数据库查询数据,第二次查询用户信息时则从一级缓存中查询到了用户信息,并且两次查询得到的用户哈希地址也相同。

12.2 二级缓存(Global Cache)

二级缓存是指在同一个 Mapper 的不同 SqlSession 之间进行的缓存。开启二级缓存需要在Mapper的映射文件中进行配置:(在UserMapper.xml进行如下配置)

<mapper namespace="com.example.mapper.UserMapper" >
	<!-- 此处定义了二级缓存的开启 -->
    <cache
            eviction="FIFO"
            flushInterval="60000"
            size="512"
            readOnly="true"/> 
</mapper>

在MyBatis的配置文件中,也需要开启全局缓存:(在全局配置文件 SqlMapConfig.xml 中加入以下内容)

<settings>
	<!-- 开启全局缓存 -->
    <setting name="cacheEnabled" value="true"/>
</settings>

开启二级缓存后,当一个 SqlSession 执行完毕并提交或关闭时,这个 Session 中的所有数据会被提交到二级缓存中。当另一个 SqlSession 需要执行相同的查询时,MyBatis 会先从二级缓存中查找是否有相应的数据,如果有,就直接返回,不再查询数据库。

注意:为了使得某个 Mapper 的某个查询开启二级缓存,该 Mapper 的对应的 POJO 需要实现序列化接口(Serializable),因为二级缓存的数据可能需要被序列化到磁盘中,以便在应用重启后能够重新加载。

测试二级缓存

public class UserMapperTest {
	private SqlSessionFactory factory = null;
	   
    //在JUnit测试中,@Before 注解标记的方法会在每个测试方法运行之前被执行。
    @Before
    public void before() throws IOException {
        String resource = "SqlMapConfig.xml";
        InputStream resourceAsStream = Resources.getResourceAsStream(resource);
        factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    }
    
    //测试二级缓存
	@Test
    public void testCache2(){
        SqlSession sqlSession1 = factory.openSession();
        SqlSession sqlSession2 = factory.openSession();
        UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
        User user1 = userMapper1.findUserById(1);
        sqlSession1.close();//需要关闭sqlSession1后再查询
        System.out.println("---------------------------------");
        UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
        User user2 = userMapper2.findUserById(1);
        System.out.println(user1==user2);
        sqlSession2.close();
    }
}

在查询用户信息之前打断点进行调试,调试结果如下:
在这里插入图片描述
从调试结果来看,第一次查询用户信息时,由于缓存中没有相应数据,所以需要通过数据库查询数据;第二次查询用户信息时,由于 sqlSession1 执行完毕并关闭,MyBatis 会先从二级缓存中查找是否有相应的数据,如果有,就直接返回,不再查询数据库。并且两次查询得到的用户哈希地址也相同。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值