MyBatis---使用篇

·  阅读 51
MyBatis---使用篇

什么是框架?

框架相当于是一个脚手架,内部已经封装好了很多代码,只需要在其基础上进行开发就可以提高我们的开发效率。

什么是MyBatis?

  • MyBatis 是一款优秀的持久层框架。

  • 它支持自定义 SQL、存储过程以及高级映射。

  • MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。

  • 官方文档:mybatis.org/mybatis-3/z…

快速上手

我的mysql版本是5.7

1. 准备数据

DROP TABLE IF EXISTS user;

CREATE TABLE user
(
    id BIGINT(20) NOT NULL COMMENT '主键ID',
    name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
    age INT(11) NULL DEFAULT NULL COMMENT '年龄',
    email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
    PRIMARY KEY (id)
);


INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');
复制代码

2. 依赖导入

<dependencies>
  
    <!-- mybatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.10</version>
    </dependency>

    <!-- mysql -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.11</version>
    </dependency>


    <!-- lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.24</version>
        <scope>provided</scope>
    </dependency>

    <!-- junit -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
  
</dependencies>
复制代码

3. JavaBean

用于映射数据库:

  • 类名 = 表名

  • 类成员变量 = 字段

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Integer id;
    private String name;
    private Integer age;
    private String email;
}
复制代码

4. 定义接口

创建 com.nhb.dao.UserDao 接口,并定义接口方法,内容如下:

public interface UserDao {
    List<User> getAll();
}
复制代码

5. xml映射实现接口

在 resources 目录下创建 com/nhb/dao/UserDao.xml,实现接口并定义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="com.nhb.dao.UserDao">
    
    <select id="getAll" resultType="com.nhb.entity.User">
        SELECT * FROM user
    </select>
    
</mapper>
复制代码

6. mybatis配置文件

在 resources 目录下创建 mybatis-config 配置文件,内容如下:

<?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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://182.61.21.196:3306/demo?useSSL=false"/>
                <property name="username" value="nhb"/>
                <property name="password" value="5740339qq"/>
            </dataSource>
        </environment>
    </environments>

    <!--  关联xml配置的文件  -->
    <mappers>
        <mapper resource="com/nhb/dao/UserDao.xml"/>
    </mappers>
</configuration>
复制代码

7. 测试单元

在 test 目录下创建 com/nhb/MyTest.java,内容如下:

public class MyTest {

    private SqlSession sqlSession;

    @Before
    public void init() throws IOException {
        //定义mybatis配置文件路径
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);

        //传入对应配置文件的输入流,读取配置文件获得SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = new
                SqlSessionFactoryBuilder().build(inputStream);

        //通过SqlSessionFactory获取sqlSession对象(理解为数据库连接)
        sqlSession = sqlSessionFactory.openSession();
    }

    @Test
    public void testUserDao(){
        //通过sqlSession获取Mapper接口的实现类
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        List<User> all = userDao.getAll();

        //遍历输出
        for (User user : all) {
            System.out.println(user);
        }
    }

    @After
    public void destroy(){
        //提交事务
        sqlSession.commit();
        //释放资源
        sqlSession.close();
    }
}
复制代码

高效开发

拒绝重复且无意义的东西,提升开发效率。

  1. IDEA 里面设置好 mybatis-config 和 UserDao.xml代码模板。
  2. IDEA 安装 MyBatisX 插件,接口与实体类互相跳转,自动生成实体类,代码生成等。
  3. 可以复制粘贴就别手写,容易出错。

参数获取

单个参数

基本参数

使用 #{} 来取值,写任意名字都可以获取参数,建议使用接口的参数名来取值。

定义接口方法

User getById(Integer id);
复制代码

实现接口方法,并定义sql

<select id="getById" resultType="com.nhb.entity.User">
    SELECT * FROM user WHERE id = #{id}
</select>
复制代码

单元测试

@Test
public void testGetById(){
    UserDao userDao = sqlSession.getMapper(UserDao.class);
    User user = userDao.getById(1);
    System.out.println(user);
}
复制代码

对象

使用对象中的属性名来获取对应的值

定义接口方法

 User getByBean(User user);
复制代码

实现接口方法,并定义sql

<select id="getByBean" resultType="com.nhb.entity.User">
    SELECT * FROM user WHERE id = #{id}
          AND name = #{name} AND age = #{age}  AND email = #{email}
</select>
复制代码

单元测试

@Test
public void testGetByBean(){
    UserDao userDao = sqlSession.getMapper(UserDao.class);
    User newUser = new User(2, "Jack", 20, "test2@baomidou.com");
    User  user = userDao.getByBean(newUser);
    System.out.println(user);
}
复制代码

多个参数

MyBatis 会把多个参数放入一个Map集合中,如果只有一个参数的时候不用做什么特殊处理。 多个参数的情况下要加上 @Param 来设置参数名。

定义接口方法

User getByMap(@Param("id") Integer id,@Param("name") String name);
复制代码

实现接口方法,并定义sql

<select id="getByMap" resultType="com.nhb.entity.User">
    SELECT * FROM user WHERE id = #{id} AND name = #{name}
</select>
复制代码

单元测试

@Test
public void testGetByMap(){
    UserDao userDao = sqlSession.getMapper(UserDao.class);
    User user = userDao.getByMap(1,"Jone");
    System.out.println(user);
}
复制代码

占位符的区别

#{} 是预编译处理,会将形参变量的值取出,并自动给其添加引号。

${} 是字符串替换,直接替换掉占位符,使用 ${} 的话会导致 sql 注入。

所以为了防止 SQL 注入,能用 #{} 的不要去用 ${}

核心类

sqlSessionFactory

sqlSessionFactory 是一个 SqlSession 的工厂类,主要用来获取 SqlSession 对象。

//成员方法

SqlSession openSession();

//获取SqlSession对象,传入的参数表示创建的SqlSession是否自动提交
SqlSession openSession(boolean var1);
复制代码

sqlSession

SqlSession 实现了 Closeable 接口,代表 SqlSession 是可以关闭的,那也就是说 SqlSession 代表一种可关闭的连接。SqlSession 提供了在数据库执行SQL命令所需的所有方法,它还提供了事务的相关操作。

//成员方法

//获取Mapper对象
<T> T getMapper(Class<T> var1); 

//提交事务
void commit(); 

//事物回滚
void rollback(); 

//释放资源
void close(); 
复制代码

CRUD操作

定义接口方法

//新增
int save(User user);

//删除
int removeById(Integer id);

//修改
int updateById(User user);

//查询
User getById(Integer id);
复制代码

实现接口方法,并定义sql

<insert id="save">
    INSERT INTO user VALUE(#{id},#{name},#{age},#{email})
</insert>
  
<delete id="removeById">
    DELETE FROM user WHERE id = #{id}
</delete>

<update id="updateById">
    UPDATE user SET name = #{name}, age = #{age}, email = #{email} WHERE id = #{id}
</update>

<select id="getById" resultType="com.nhb.entity.User">
    SELECT * FROM user WHERE id = #{id}
</select>
复制代码

单元测试

@Test
public void testSave(){
    UserDao userDao = sqlSession.getMapper(UserDao.class);
    User newUser = new User(6, "鸡你太美", 25, "jige@baomidou.com");
    int result = userDao.save(newUser);
    System.out.println(result);
}

@Test
public void testRemoveById(){
    UserDao userDao = sqlSession.getMapper(UserDao.class);
    int result = userDao.removeById(5);
    System.out.println(result);
}


@Test
public void testUpdateById(){
    UserDao userDao = sqlSession.getMapper(UserDao.class);
    User newUser = new User(6, "鸡哥真爱粉", 21, "jige@www.com");
    int result = userDao.updateById(newUser);
    System.out.println(result);
}


@Test
public void testGetById(){
    UserDao userDao = sqlSession.getMapper(UserDao.class);
    User user = userDao.getById(1);
    System.out.println(user);
}
复制代码

配置文件

下面举几个简单的配置来演示一下,更多配置查官方文档:mybatis.org/mybatis-3/z…

properties

可以使用 <properties><properties/> 读取 properties 配置文件,使用其中的 resource 属性来设置配置文件的路径。然后使用 ${key} 来获取配置文件中的值。

在 resources 目录下有 jdbc.properties 文件:

jdbc.url = jdbc:mysql://localhost:3306/demo?useSSL=false
jdbc.driver = com.mysql.cj.jdbc.Driver
jdbc.username = nhb
jdbc.password = ********
复制代码

在 mybatis-config.xml 中:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<!--核心配置文件-->
<configuration>
    <!-- 设置配置文件所在的路径-->
    <properties resource="jdbc.properties"/>

    <environments default="development">
        <environment id="development">
            <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>

    <!--  关联xml配置的文件  -->
    <mappers>
        <mapper resource="mapper/UserDao.xml"/>
    </mappers>
</configuration>
复制代码

settings

可以使用该标签来进行一些设置

<settings>
    <!-- 开启自动驼峰命名映射-->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
复制代码

environments

配置数据库相关的环境,例如事物管理器,连接池相关参数等。

<!-- 设置默认环境-->
<environments default="development">
    <!-- 设置环境唯一标识符-->
    <environment id="development">
        <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>
复制代码

mappers

将包内的映射器接口实现全部注册为映射器

注意:定义dao接口所在的包,要求xml文件存放的路径和dao接口的包名要对应,不然会报错!!

<mappers>
    <package name="com.nhb.dao"/>
</mappers>
复制代码

续MyBatis快速入门

输出日志

log4j 配置,在 resources 目录下创建 log4j.properties 文件

#声明日志的输出级别及输出方式
log4j.rootLogger=DEBUG,stdout

# MyBatis 日志配置
log4j.logger.org.mybatis.example.BlogMapper=TRACE

#声明日志的输出位置在控制台输出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

#定义日志的打印格式  %t 表示线程名称  %5p表示输出日志级别   %n表示换行
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
复制代码

导入依赖

<!-- log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
复制代码

动态SQL

在实际开发中的 SQL 语句没有之前的这么简单,很多时候需要根据传入的参数情况动态的生成 SQL 语句。MyBatis 提供了动态 SQL 相关的标签让我们使用。

if

使用if标签进行条件判断,条件成立才会把if标签中的内容拼接进 sql 语句中

定义接口

User getByName(@Param("id") Integer id ,@Param("name") String name);
复制代码

实现接口的方法,并定义sql

<select id="getByName" resultType="com.nhb.entity.User">
    SELECT * FROM user WHERE id = #{id}
    <if test="name!=null">
       AND  name = #{name}
    </if>
</select>
复制代码

name 为空执行的 sql:SELECT * FROM user WHERE id = ?

参数都不为空执行的 sql:SELECT * FROM user WHERE id = ? AND name = ?

where

等价于

<trim prefix="where" prefixOverrides="and|or"></trim>
复制代码

prefix:动态添加的前缀,如果标签中有内容就会添加上设置的前缀

prefixOverrides:清除前缀,多个值可以用|分隔

有参数做条件查询,没有参数查询所有,可以使用 where 标签动态的拼接去除前缀的 and 或者or

定义接口

List<User> getByName(@Param("id") Integer id ,@Param("name") String name);
复制代码

实现接口的方法,并定义sql

<select id="getByName" resultType="com.nhb.entity.User">
    SELECT * FROM user
    <where>
        <if test="id!=null">
            id = #{id}
        </if>

        <if test="name!=null">
            AND  name = #{name}
        </if>
    </where>
</select>
复制代码

name 为空执行:SELECT * FROM user WHERE id = ?

两个参数不为空SELECT * FROM user WHERE id = ? AND name = ?

两个参数为空SELECT * FROM user

set

等价于

<trim prefix="set" suffixOverrides="," ></trim>
复制代码

prefix:动态添加的前缀,如果标签中有内容就会添加上设置的前缀

suffixOverrides:清除的后缀,多个值可以用|分隔

修改某些字段,但是不想动其他字段的时候,可以使用 set 标签动态的拼接去除后缀的逗号

实现接口的方法,并定义sql

int updateById(User user);
复制代码

xml实现类

<update id="updateById">
    UPDATE user
    <set>
        <if test="name!=null">
            name = #{name},
        </if>

        <if test="age!=null">
            age = #{age},
        </if>

        <if test="email!=null">
            email = #{email},
        </if>
    </set>

    WHERE id = #{id}
</update>
复制代码

如果调用方法时传入的 id 为1,name 不为 null ,其他属性为 null,最终执行的 sql :UPDATE user SET name = ? WHERE id = ?

foreach

使用 id 做批量操作时,可以使用 foreach 标签遍历集合或者数组类型的参数,获取其中的元素拿来动态拼接 SQL 语句

接口方法

List<User> selectByIds(@Param("ids") Integer[] ids);
复制代码

实现接口的方法,并定义sql

<select id="selectByIds" resultType="com.nhb.entity.User">
    SELECT * FROM user
        <where>
            <foreach collection="ids" open="id in(" close=")" item="id" separator=",">
                #{id}
            </foreach>
        </where>
</select>
复制代码
  • collection:表示要遍历的参数。

  • open:表示遍历开始时拼接的语句

  • item:表示给当前遍历到的元素的取的名字

  • separator:表示每遍历完一次拼接的分隔符

测试单元

//调用测试方法
Integer[] ids = {1,2,3,4};
List<User> userList = userMapper.selectByIds(ids);
System.out.println(userList);
复制代码

choose

当我们不想使用所有的条件,而只是想从多个条件中选择一个使用时,可以使用 choose系列标签,类似于Java中的 switch

定义接口

User selectChose(@Param("id") Integer id,@Param("name") String name);
复制代码

实现接口的方法,并定义sql

<select id="selectChose" resultType="com.nhb.entity.User">
    SELECT * FROM user
        <where>
            <choose>
                <when test="id!=null">
                    id = #{id}
                </when>
                <when test="name!=null">
                    name = #{name}
                </when>
                <otherwise>
                    id = 3
                </otherwise>
            </choose>
        </where>
</select>
复制代码
  • choose 类似于Java中的 switch

  • when 类似于Java中的 case

  • otherwise 类似于Java的 dufault

SQL片段抽取

我们在 xml 映射文件中编写 SQL 语句的时候可能会遇到重复的 SQL 片段,这种SQL 片段我们可以使用 sql 标签来进行抽取,然后在需要使用的时候使用 include 标签进行使用。

<!--抽取重复-->
<sql id="beanSql"> id,name,age,email</sql>

<select id="selectById" resultType="com.nhb.entity.User">
    SELECT <include refid="beanSql"/> FROM user WHERE id = #{id}
</select>
复制代码

最终执行的 sql 为:SELECT id,name,age,email FROM user WHERE id = ?

多表操作

准备数据

CREATE DATABASE /*!32312 IF NOT EXISTS*/`demo` /*!40100 DEFAULT CHARACTER SET utf8 */;

USE `demo`;
DROP TABLE IF EXISTS `orders`;
CREATE TABLE `orders` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `createtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  `price` int(11) DEFAULT NULL COMMENT '价格',
  `remark` varchar(100) DEFAULT NULL COMMENT '备注',
  `user_id` int(11) DEFAULT NULL COMMENT '用户id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
insert  into `orders`(`id`,`createtime`,`price`,`remark`,`user_id`) values (1,'2014-06-26 16:55:43',2000,'无',2),(2,'2021-02-23 16:55:57',3000,'无',3),(3,'2021-02-23 16:56:21',4000,'无',2);
DROP TABLE IF EXISTS `role`;

CREATE TABLE `role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) DEFAULT NULL COMMENT '角色名',
  `desc` varchar(100) DEFAULT NULL COMMENT '角色描述',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

/*Data for the table `role` */

insert  into `role`(`id`,`name`,`desc`) values (1,'总经理','一人之下'),(2,'CFO',NULL);

/*Table structure for table `user` */

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `address` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=33 DEFAULT CHARSET=utf8;

/*Data for the table `user` */

insert  into `user`(`id`,`username`,`age`,`address`) values (2,'pdd',26,NULL),(3,'UZI',19,'上海11'),(4,'RF',19,NULL);

/*Table structure for table `user_role` */

DROP TABLE IF EXISTS `user_role`;

CREATE TABLE `user_role` (
  `user_id` int(11) DEFAULT NULL,
  `role_id` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `user_role` */

insert  into `user_role`(`user_id`,`role_id`) values (2,2),(2,1),(3,1);
复制代码

JavaBean

正经人谁自己写实体类,使用Easy Code 插件生成实体它不香吗?

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Orders implements Serializable {
    private static final long serialVersionUID = 622876873390228933L;
    
    private Integer id;
    /**
     * 创建时间
     */
    private Date createtime;
    /**
     * 价格
     */
    private Integer price;
    /**
     * 备注
     */
    private String remark;
    /**
     * 用户id
     */
    private Integer userId;

}


@Data
@AllArgsConstructor
@NoArgsConstructor
public class Role implements Serializable {
    private static final long serialVersionUID = -64026172101506006L;
    
    private Integer id;
    /**
     * 角色名
     */
    private String name;
    /**
     * 角色描述
     */
    private String desc;

}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    private static final long serialVersionUID = 104450763022002404L;
    
    private Integer id;
    
    private String username;
    
    private Integer age;
    
    private String address;

}



@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserRole implements Serializable {
    private static final long serialVersionUID = 475345861543553325L;
    
    private Integer userId;
    
    private Integer roleId;

}
复制代码

ResultMap

基本使用

当实体类中的属性与数据库中的字段不符合预期,我们可以使用 resultMap 标签自定义结果集和实体类属性的映射规则

<!--
1. resultMap 用来自定义结果集和实体类的映射
    属性:
        id 相当于这个resultMap的唯一标识
        type 用来指定映射到哪个实体类

2. id标签  用来指定主键列的映射规则
    属性:
        property 要映射的属性名
        column  对应的列名

3. result标签 用来指定普通列的映射规则
    属性:
        property 要映射的属性名
        column 对应的列名
-->

<resultMap id="orderMap" type="com.nhb.entity.Orders">
    <id column="id" property="id"/>
    <result column="createtime" property="createtime"/>
    <result column="price" property="price"/>
    <result column="remark" property="remark"/>
    <result column="user_id" property="userId"/>
</resultMap>

<!-- 使用我们自定义的映射规则 -->
<select id="findAll" resultMap="orderMap">
    SELECT id, createtime, price, remark, user_id FROM orders
</select>
复制代码

自动映射

我们定义 resultMap 时默认情况下自动映射是开启状态的,也就是如果结果集的列名和我们的属性名相同是会自动映射的,我们只需要写特殊情况的映射关系即可。

<resultMap id="orderMap" type="com.nhb.entity.Order" >
    <result column="user_id" property="userId"></result>
</resultMap>

<!--使用我们自定义的映射规则-->
<select id="findAll" resultMap="orderMap">
    SELECT id,createtime,price,remark,user_id  FROM ORDERS
</select>
复制代码

多表查询时自动映射可能会出现问题,所以我们需要把 resultMapautoMapping 属性设置为 false

<resultMap id="orderMap" type="com.nhb.entity.Order" autoMapping="false">
    <id column="id" property="id"></id>
    <result column="createtime" property="createtime"></result>
    <result column="price" property="price"></result>
    <result column="remark" property="remark"></result>
    <result column="user_id" property="userId"></result>
</resultMap>
复制代码

继承映射

我们可以使用 resultMapextends 属性来指定一个 resultMap,从而复用重复的映射关系配置。

<!--定义个父映射,供其他resultMap继承-->
<resultMap id="baseOrderMap" type="com.nhb.entity.Order" >
    <id column="id" property="id"></id>
    <result column="createtime" property="createtime"></result>
    <result column="price" property="price"></result>
    <result column="remark" property="remark"></result>
</resultMap>

<!--继承baseOrderMap,然后只需要写自己特有的映射关系即可-->
<resultMap id="orderMap" type="com.nhb.entity.Order" autoMapping="false" extends="baseOrderMap">
    <result column="user_id" property="userId"></result>
</resultMap>
复制代码

一对一关系

两个实体之间是一对一的关系,(例如我们需要查询订单,要求还需要下单用户的数据,这里的订单相对于用户是一对一。)

接口定义方法

//根据订单id查询订单,要求把下单用户的信息也查询出来
Order findById(Integer id);
复制代码

因为期望 Order 中还能包含下单用户的数据,所以可以再 Order 类中增加一个属性

private User user;
复制代码

使用 ResultMap 中的子标签 association 来设置关联实体类的映射规则

<resultMap id="baseOrderMap" type="com.nhb.entity.Order" >
    <id column="id" property="id"></id>
    <result column="createtime" property="createtime"></result>
    <result column="price" property="price"></result>
    <result column="remark" property="remark"></result>
</resultMap>

<resultMap id="orderMap" type="com.nhb.entity.Order" autoMapping="false" extends="baseOrderMap">
    <result column="user_id" property="userId"></result>
</resultMap>

<!--Order和User关联的映射(使用association)-->
<resultMap id="orderAssociation" type="com.nhb.entity.Order" autoMapping="false" extends="orderMap">
    <association property="user" javaType="com.nhb.entity.User">
        <id property="id" column="uid"></id>
        <result property="username" column="username"></result>
        <result property="age" column="age"></result>
        <result property="address" column="address"></result>
    </association>
</resultMap>
复制代码

使用我们定义好的 resultMap 映射规则

<!--根据订单id查询订单,要求把下单用户的信息也查询出来-->
<select id="findById" resultMap="orderAssociation">
    SELECT
        o.`id`,o.`createtime`,o.`price`,o.`remark`,o.`user_id`,u.`id` uid,u.`username`,u.`age`,u.`address`
    FROM
        orders o,`user` u
    WHERE
        o.id = #{id} AND
        o.`user_id`=u.`id`
</select>
复制代码

一对多关系

两个实体之间是一对多的关系,(例如我们需要查询用户,要求还需要该用户所具有的角色信息,这里的用户相对于角色是一对多的。)

接口定义方法

//根据id查询用户,并且要求把该用户所具有的角色信息也查询出来
User findById(Integer id);
复制代码

因为期望 User 中还能包含该用户所具有的角色信息,所以可以在 User 类中增加一个属性

//该用户所具有的角色
private List<Role> roles;
复制代码

使用 ResultMap 中的子标签 association 来设置关联实体类的映射规则

<!--定义User基本属性映射规则-->
<resultMap id="userMap" type="com.nhb.entity.User">
    <id property="id" column="id"></id>
    <result property="username" column="username"></result>
    <result property="age" column="age"></result>
    <result property="address" column="address"></result>
</resultMap>

<resultMap id="userRoleMap" type="com.nhb.entity.User"  extends="userMap">
    <collection property="roles" ofType="com.nhb.entity.Role" >
        <id property="id" column="rid"></id>
        <result property="name" column="name"></result>
        <result property="desc" column="desc"></result>
    </collection>
</resultMap>
复制代码

使用我们定义好的 resultMap 映射规则

<select id="findById" resultMap="userRoleMap" >
    SELECT 
        u.`id`,u.`username`,u.`age`,u.`address`,r.id rid,r.name,r.desc
    FROM 
        USER u,user_role ur,role r
    WHERE 
        u.id=ur.user_id AND ur.role_id = r.id
        AND u.id = #{id}
</select>
复制代码

分步查询

如果有多表查询的需求我们可以选择使用多次查询的方式来查询出我们想要的数据,Mybatis也提供了对应的配置。

例如:我们要查询用户,还要求要查询出该用户所具有的角色信息,可以先查询User表的用户信息,然后再去查询关联的角色信息。

分步查询的步骤:

  1. 查询 User 表

  2. 根据用户的 id 查询 Role 所以我们需要定义下面两个方法,并且把对应的标签也先写好

查询User

//根据用户名查询用户,并且要求把该用户所具有的角色信息也查询出来
User findByUsername(String username);
复制代码
<!--根据用户名查询用户-->
<select id="findByUsername" resultType="com.nhb.entity.User">
    select id,username,age,address from user where username = #{username}
</select>
复制代码

根据user_id查询Role

public interface RoleDao {
	//根据userId查询所具有的角色
    List<Role> findRoleByUserId(Integer userId);
}
复制代码
<!--根据userId查询所具有的角色-->
<select id="findRoleByUserId" resultType="com.nhb.entity.Role">
    select 
        r.id,r.name,r.desc
    from 
        role r,user_role ur
    where 
        ur.role_id = r.id
        and ur.user_id = #{userId}
</select>
复制代码

配置分步查询

我们期望的效果是调用 findByUsername 方法查询出来的结果中就包含角色的信息,所以我们可以设置 findByUsername 方法的 RestltMap ,指定分步查询

<resultMap id="userMap" type="com.nhb.entity.User">
    <id property="id" column="id"></id>
    <result property="username" column="username"></result>
    <result property="age" column="age"></result>
    <result property="address" column="address"></result>
</resultMap>

<!--
    select属性:指定用哪个查询来查询当前属性的数据 写法:包名.接口名.方法名
    column属性:设置当前结果集中哪列的数据作为select属性指定的查询方法需要参数
 -->

<resultMap id="userRoleMapBySelect" type="com.nhb.entity.User" extends="userMap">
    <collection property="roles"
                ofType="com.nhb.entity.Role"
                select="com.nhb.dao.RoleDao.findRoleByUserId"
                column="id">
    </collection>
</resultMap>
复制代码

指定 findByUsername 使用我们刚刚创建的 resultMap

<!--根据用户名查询用户-->
<select id="findByUsername" resultMap="userRoleMapBySelect">
    select id,username,age,address from user where username = #{username}
</select>
复制代码

设置按需加载

我们可以设置按需加载,这样在我们代码中需要用到关联数据的时候才会去查询关联数据。有两种方式可以配置分别是全局配置局部配置

局部配置

设置 fetchType 属性为 lazy

<resultMap id="userRoleMapBySelect" type="com.sangeng.pojo.User" extends="userMap">
    <collection property="roles"
                ofType="com.sangeng.pojo.Role"
                select="com.sangeng.dao.RoleDao.findRoleByUserId"
                column="id" fetchType="lazy">
    </collection>
</resultMap>
复制代码

全局配置

设置 lazyLoadingEnabled 属性为 true

<settings>
   <setting name="lazyLoadingEnabled" value="true"/>
</settings>
复制代码

分页插件

PageHelper 非常方便的帮我们实现分页查询的需求,不需要自己在 SQL 中拼接 SQL 相关参数,并且能非常方便的获取的总页数总条数等分页相关数据。

引入依赖

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>4.0.0</version>
</dependency>
复制代码

配置插件

<plugins>
    <!-- 注意:分页助手的插件  配置在通用馆mapper之前 -->
    <plugin interceptor="com.github.pagehelper.PageHelper">
        <!-- 指定方言 -->
        <property name="dialect" value="mysql"/>
    </plugin>
</plugins>
复制代码

设置分页参数

//设置分页参数
UserDao userDao = session.getMapper(UserDao.class);

//设置分页查询参数
PageHelper.startPage(1,1);
List<User> users = userDao.findAll();
System.out.println(users.get(0));
复制代码

获取其他数据

如果需要获取总页数总条数等分页相关数据,只需要创建一个 PageInfo 对象,把刚刚查询出的返回值做为构造方法参数传入,然后使用 pageInfo 对象获取即可。

PageInfo<User> pageInfo = new PageInfo<User>(users);
System.out.println("总条数:"+pageInfo.getTotal());
System.out.println("总页数:"+pageInfo.getPages());
System.out.println("当前页:"+pageInfo.getPageNum());
System.out.println("每页显示长度:"+pageInfo.getPageSize());
复制代码

注意

一对多的多表查询时,如果使用了 PageHelper 进行分页,会出现关联数据不全的情况,我们可以使用分步查询的方式解决该问题。

MyBatis缓存

Mybatis 的缓存其实就是把之前查到的数据存入内存,下次如果还是查相同的东西,就可以直接从缓存中取,从而提高效率。

一级缓存

以下的情况不会使用一级缓存:

1. 调用相同方法但是传入的参数不同
2. 调用相同方法参数也相同,但是使用的是另外一个 `sqlSession`
3. 如果查询完后,对同一个表进行了增,删改的操作,都会清空这 `sqlSession`上的缓存
4. 如果手动调用 `sqlSession``clearCache` 方法清除缓存了,后面也使用不了缓存
复制代码

二级缓存

注意:只在 sqlsession 调用了 close 或者 commit 方法后的数据才会进入二级缓存。

二级缓存在实际开发中基本不会使用,因为在分布式的情况下可能会出现幻读读情况。

分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改