Mybatis框架概述

mybatis是一个基于java的持久成框架,内部封装了jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程,使用了ORM思想实现了结果集的封装。

mybatis通过xml或注解的方式把将要执行的各种statement配置起来,并通过java对象和statement中的sql的动态参数进行映射生成最终执行的sql语句,最后有mybatis框架执行sql并将结果映射为java对象并返回。

ORM:Object Relational Mapping 对象关系映射(把数据库表和实体类及实体类的属性对应起来,让我们可以操作实体类就实现操作数据库表)

Mybatis的入门

mybatis的环境搭建

day01_01mybatis

1) 创建maven工程并导入坐标

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

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
      
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

2) 创建实体类和dao的接口

cn.itcast.domain.User

@Data
public class User implements Serializable {
    private  Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
}

cn.itcast.dao.UserDao

public interface UserDao {
    
    /** 
    * @description: 查询所有操作 
    * @param: [] 
    * @return: java.util.List<cn.itcast.domain.User> 
    * @author: Silince 
    * @date: 2020-03-10 
    */ 
    List<User> findAll();
}

3) 创建mybatis的主配置文件 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">
<!--mybatis的主配置文件-->
<configuration>
    <!--    配置环境-->
    <environments default="mysql">
        <!--        配置mysql的环境-->
        <environment id="mysql">
            <!--            配置事务的类型-->
            <transactionManager type="JDBC"></transactionManager>
            <!--            配置数据源/连接池-->
            <dataSource type="POOLED">
                <!--                配置链接数据库的4个基本信息-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="*******"/>
            </dataSource>
        </environment>
    </environments>

    <!--    指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件-->
    <mappers>
        <mapper resource="cn/itcast/dao/UserDao.xml"/>
    </mappers>

</configuration>

4) 创建映射配置文件 UserDao.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.itcast.dao.UserDao">
<!--    配置查询所有-->
    <select id="findAll" resultType="cn.itcast.domain.User">
        select * from user
    </select>
</mapper>

5) 编写测试类

入门案例的分析

public class MybatisTest {
    public static void main(String[] args) throws IOException {
        //1 读取配置文件
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2 创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(in);
        //3 使用工厂生产一个SqlSession对象
        SqlSession session = factory.openSession();
        //4 使用SqlSession创建Dao接口的代理对象
        UserDao userDao=session.getMapper(UserDao.class);
        //5 使用代理对象执行dao中的方法
        List<User> users = userDao.findAll();
        for (User user : users) {
            System.out.println(user);
        }
        //6 释放资源
        session.close();
        in.close();
    }
}

环境搭建的注意事项

当我们遵从了3,4,5点之后,我们在开发中就无须再写dao的实现类

  1. 创建UserDao.xml和UserDao.java时名称是为了和之前的知识保持一致。在Mybatis中它把持久成的操作接口名称和映射文件叫做Mapper,所以UserDao和UserMapper是一样的。

  2. 在Idea中创建目录的时候,它和包是不一样的。

    包在创建时: cn.silince.dao 是三级结构

    目录在创建时: cn.silince.dao 是一级目录

  3. mybatis的映射配置文件位置必须和dao接口的包结构相同
  4. 映射配置文件的mapper标签namespace属性的值必须是dao接口的全限定类名
  5. 映射配置文件的操作配置(select),id属性的值必须是dao接口的方法名

基于注解的入门案例 🤔

把UserDao.xml移除,在dao接口的方法上使用@Select注解,并指定sql语句,同时需要在SqlMapConfig.xml中的mapper配置时候,使用class属性指定到dao接口的全限定类名。

在实际开发中,都是越简便越好,所以都是采用不写dao实现类的方式,不管使用xml还是注解配置,但是mybatis他是支持写dao实现类的。

UserDao:

public interface UserDao {
    @Select("select * from user")
    List<User> findAll();
}

SqlMapConfig.xml:

<!--    指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件
  如果是使用注解来配置的话,此处应该使用class属性指定被注解的dao全限定类名
  -->
<mappers>
  <mapper class="cn.itcast.dao.UserDao"/>
</mappers>

自定义 Mybatis 框架

自定义mybatis流程分析

自定义Mybatis分析

自定义mybatis开发流程图

基于代理 Dao 实现 CRUD 操作

配置文件实现 注解实现

OGNL表达式

他是通过对象的取值方法来获取数据,在写法上把get省略了,语法格式就是使用 #{对象.对象}的方式。

比如我们获取用户的名称

  • 类中的写法: user.getUsername();
  • OGNL表达式写法: user.username
  • mybatis中为什么能直接写username,而不用 user. 呢?
    • ANSWER:因为在parameterType中已经提供了属性所属的类,所以不需要写对象名。
/**
  * @description: 根据QueryVO查询中的查询条件查询用户  QueryVo为查询条件对象
  */
@Select("select * from user where username like #{user.username}")
int findUserByVo(QueryVo vo);

CRUD操作的实现

根据 ID 查询

1)在持久层接口中添加findById方法

public interface IUserDao {
/**
     * 根据id查询用户信息
     * @param userId
     * @return
     */
    User findById(Integer userId);
}

2)在用户的映射配置文件中配置

  • resultType 属性:用于指定结果集的类型。
  • parameterType 属性:用于指定传入参数的类型。
  • #{}中内容的写法:由于数据类型是基本类型,所以此处可以随意写。
  • sql 语句中使用#{}字符:
    • 它代表占位符,相当于原来 jdbc 部分所学的 ?,都是用于执行语句时替换实际的数据。
    • 具体的数据是由 #{} 里面的内容决定的。
<!-- 根据id查询用户 -->
<select id="findById" parameterType="INT" resultType="com.itheima.domain.User">
  select * from user where id = #{uid}
</select>

3)在测试类添加测试

public class MybatisTest {

    private InputStream in;
    private IUserDao userDao;

    @Before//用于在测试方法执行之前执行
    public void init()throws Exception{
        //1.读取配置文件,生成字节输入流
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.获取SqlSessionFactory
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //3.使用工厂对象,创建dao对象
        userDao = new UserDaoImpl(factory);
    }

    @After//用于在测试方法执行之后执行
    public void destroy()throws Exception{
        //6.释放资源
        in.close();
    }
  
 
   /**
     * 测试查询操作
     */
  	@Test
    public void testFindOne(){
        //5.执行查询一个方法
        User  user = userDao.findById(50);
        System.out.println(user);
    }
  
}

保存操作

在用户的映射配置文件中配置

  • #{}中内容的写法
    • 由于我们保存方法的参数是 一个 User 对象,此处要写 User 对象中的属性名称。
    • 它用的是 ognl 表达式
    • #{user.username}它会先去找 user 对象,然后在 user 对象中找到 username 属性,并调用 getUsername()方法把值取出来。但是我们在 parameterType 属性上指定了实体类名称,所以可以省略 user. 而直接写 username。
<!-- 保存用户 -->
<insert id="saveUser" parameterType="com.itheima.domain.User">
  insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday});
</insert>

问题扩展:新增用户id的返回值

新增用户后,同时还要返回当前新增用户的 id 值,因为 id 是由数据库的自动增长来实现的,所以就相当于我们要在新增后将自动增长 auto_increment 的值返回。

<insert id="saveUser" parameterType="com.itheima.domain.User">
  <!-- 配置插入操作后,获取插入数据的id -->
  <selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
    select last_insert_id();
  </selectKey>
  insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday});
</insert>

用户更新

<!-- 更新用户 -->
<update id="updateUser" parameterType="com.itheima.domain.User">
  update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id}
</update>

用户删除

<!-- 删除用户-->
<delete id="deleteUser" parameterType="java.lang.Integer">
  delete from user where id = #{uid}
</delete>

查询使用聚合函数

<!-- 查询总记录条数 -->
<select id="findTotal" resultType="int"> select count(*) from user;
</select>

用户模糊查询

1)在用户的映射配置文件中配置

<!-- 根据名称模糊查询 -->
<select id="findByName" parameterType="string" resultType="com.itheima.domain.User">
  select * from user where username like #{name}
</select>

2)测试方法

@Test
public void testFindByName(){
	List<User> users = userDao.findByName("%王%"); for(User user : users){
	System.out.println(user); 
}
}

我们在配置文件中没有加入%来作为模糊查询的条件,所以在传入字符串实参时,就需要给定模糊查询的标识%。配置文件中的#{username}也只是一个占位符,所以 SQL 语句显示为“?”

在控制台输出的执行 SQL 语句如下:

image-20201026175427656

模糊查询的另一种配置方式

我们在上面将原来的#{}占位符,改成了${value}。注意如果用模糊查询的这种写法,那么${value}的写法就是固定的,不能写成其它名字。

<!-- 根据名称模糊查询 -->
<select id="findByName" parameterType="string" resultType="com.itheima.domain.User">
	select * from user where username like '%${value}%'
</select>

在控制台输出的执行 SQL 语句如下:

image-20201026181540134

#{}${}的区别

  • #{}表示一个占位符号

    通过#{}可以实现 preparedStatement 向占位符中设置值,自动进行 java 类型和 jdbc 类型转换,#{}可以有效防止 sql 注入。 #{}可以接收简单类型值或 pojo 属性值。 如果 parameterType 传输单个简单类型值,#{}括号中可以是 value 或其它名称。

  • ${}表示拼接 sql 串

    通过${}可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换, ${}可以接收简单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值,${}括号中只能是 value。

注意事项

  1. 持久层接口和持久层接口的映射配置必须在相同的包下
  2. 持久层映射配置中 mapper 标签的 namespace 属性取值必须是持久层接口的全限定类名
  3. SQL 语句的配置标签<select>,<insert>,<delete>,<update>的 id 属性必须和持久层接口的方法名相同。

Mybatis 的参数深入

parameterType 配置参数

SQL 语句传参,使用标签的 parameterType 属性来设定。该属性的取值可以是基本类型,引用类型(例如:String 类型),还可以是实体类类型(POJO 类)。同时也可以使用实体类的包装类,本章节将介绍如何使用实体类的包装类作为参数传递。

⚠️注意事项:

  • 基本类型和 String 我们可以直接写类型名称,也可以使用包名.类名的方式,例如:java.lang.String。
  • 实体类类型,目前我们只能使用全限定类名。
  • 究其原因,是 mybaits 在加载时已经把常用的数据类型注册了别名,从而我们在使用时可以不写包名,而我们的是实体类并没有注册别名,所以必须写全限定类名。

这些都是支持的默认别名:

image-20201026182624638

传递 pojo 包装对象

开发中通过 pojo 传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。

Pojo 类中包含 pojo。

需求:根据用户名查询用户信息,查询条件放到 QueryVo 的 user 属性中。

编写QueryVo

/**
 * @program: day02_mybatisCRUD
 * @description: 查询条件对象
 * @author: Silince
 **/
public class QueryVo {
    private User user;

    public User getUser() {
        return user;
    }

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

编写持久层接口

public interface UserDao {
   /**
     * @description: 根据QueryVO查询中的查询条件查询用户
     */
    //@Select("select * from user where username like #{user.username}")
    List<User> findUserByVo(QueryVo vo);
}

持久层接口的映射文件

<!-- 根据用户名称模糊查询,参数变成一个 QueryVo 对象了 -->
<select id="findByVo" resultType="com.itheima.domain.User"
        parameterType="com.itheima.domain.QueryVo"> select * from user where username like #{user.username};
</select>

测试包装类

@Test
public void testFindByQueryVo() { 
  QueryVo vo = new QueryVo(); 
  User user = new User();
	user.setUserName("%王%"); 
  vo.setUser(user);
	List<User> users = userDao.findByVo(vo);
	for(User u : users) { 
    System.out.println(u);
	}
}

Mybatis 的输出结果封装

resultType 配置结果类型

resultType 属性可以指定结果集的类型,它支持基本类型和实体类类型。

需要注意的是,它和 parameterType 一样,如果注册过类型别名的,可以直接使用别名。没有注册过的必须使用全限定类名。

同时,当是实体类名称是,还有一个要求,实体类中的属性名称必须和查询语句中的列名保持一致,否则无法实现封装。

修改映射配置

使用别名查询

<!-- 配置查询所有操作 -->
<select id="findAll" resultType="com.itheima.domain.User">
  select id as userId,username as userName,birthday as userBirthday, sex as userSex,address as userAddress from user
</select>

如果我们的查询很多,都使用别名的话写起来岂不是很麻烦,有没有别的解决办法呢?

resultMap 结果类型

resultMap 标签可以建立查询的列名和实体类的属性名称不一致时建立对应关系。从而实现封装。

在 select 标签中使用 resultMap 属性指定引用即可。同时 resultMap 可以实现将查询结果映射为复杂类型的 pojo,比如在查询结果映射对象中包括 pojo 和 list 实现一对一查询和一对多查询。

定义resultMap

  • id 标签:用于指定主键字段
  • result 标签:用于指定非主键字段
  • column 属性:用于指定数据库列名
  • property 属性:用于指定实体类属性名称
<!-- 建立 User 实体和数据库表的对应关系
			type 属性:指定实体类的全限定类名
			id 属性:给定一个唯一标识,是给查询 select 标签引用用的。 -->
<resultMap type="com.itheima.domain.User" id="userMap"> <id column="id" property="userId"/>
  <result column="username" property="userName"/>
  <result column="sex" property="userSex"/>
  <result column="address" property="userAddress"/>
  <result column="birthday" property="userBirthday"/> 
</resultMap>

映射配置

<!-- 配置查询所有操作 -->
<select id="findAll" resultMap="userMap"> 
  select * from user
</select>

SqlMapConfig.xml配置文件

SqlMapConfig.xml中配置的内容和顺序

-properties(属性) 
	--property
-settings(全局配置参数) 
	--setting
-typeAliases(类型别名) 
	--typeAliase
	--package
-typeHandlers(类型处理器)
-objectFactory(对象工厂) 
-plugins(插件) 
-environments(环境集合属性对象)
	--environment(环境子属性对象)
		---transactionManager(事务管理) 
		---dataSource(数据源)
-mappers(映射器) 
	--mapper
	--package

properties(属性)

在使用 properties 标签配置时,我们可以采用两种方式指定属性配置。

<properties>
  <property name="jdbc.driver" value="com.mysql.jdbc.Driver"/> <property name="jdbc.url" value="jdbc:mysql://localhost:3306/eesy"/>
  <property name="jdbc.username" value="root"/>
  <property name="jdbc.password" value="1234"/> 
</properties>

或在 classpath 下定义 db.properties

jdbc.driver=com.mysql.jdbc.Driver 
jdbc.url=jdbc:mysql://localhost:3306/eesy 
jdbc.username=root
jdbc.password=1234

typeAliases(类型别名)

在前面我们讲的 Mybatis 支持的默认别名,我们也可以采用自定义别名方式来开发。

在 SqlMapConfig.xml 中配置:

</typeAliases>
	<!-- 单个别名定义 -->
	<typeAlias alias="user" type="com.itheima.domain.User"/>
	<!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以) -->
	<package name="com.itheima.domain"/> 
	<package name="其它包"/>
</typeAliases>

mappers(映射器)

  • <mapperresource=""/>
    • 使用相对于类路径的资源
    • 如:<mapper resource="com/itheima/dao/IUserDao.xml" />
  • <mapperclass=""/>
    • 使用 mapper 接口类路径
    • 如:<mapper class="com.itheima.dao.UserDao"/>
    • 注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。
  • <package name=""/>
    • 注册指定包下的所有 mapper 接口
    • 如:<package name="cn.itcast.mybatis.mapper"/>
    • 注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。

Mybatis连接池与事务

连接池

在实际开发中都会使用连接池,因为他可以减少我们获取链接所消耗的时间。

  • 连接池就是用于存储连接的一个容器
  • 容器其实就是一个集合对象,该集合必须是线程安全的,不能让两个线程拿到同一个连接
  • 该集合还必须实现队列的特性:先进先出

Mybatis中的连接池

mybatis提供了3种方式的配置

配置的位置:主配置文件SqlMapConfig.xml中的dataSource标签,type属性就是表示采用何种连接池方式。

type属性的取值:

  • ⭐️POOLED 采用传统的javax.sql.DataSource规范中的连接池,mybatis有该规范的实现
  • UNPOOLED 采用传统获取连接的方式,也实现了javax.sql.DataSource接口,但是没有使用池的思想
  • JNDI 采用服务器提供的技术JNDI技术实现,来获取DataSource对象,不同的服务器能拿到的DataSource对象是不一样的。ps:如果不是web或者maven的war工程,不能使用。(tomcat服务器采用的是dbcp连接池)

无标题2

Mybatis中的事务

Mybatis中是通过SqlSession对象的commit方法和rollback方法实现事务的提交和回滚,本质上就是调用 JDBC 的 setAutoCommit()来实现事务控制。

Mybatis自动提交事务的设置

为什么 CUD 过程中必须使用 sqlSession.commit()提交事务?主要原因就是在连接池中取出的连接,都会将调用 connection.setAutoCommit(false)方法,这样我们就必须使用 sqlSession.commit()方法,相当于使用了 JDBC 中的 connection.commit()方法实现事务提交。

// 创建 SqlSession 对象
session = factory.openSession(true);// 自动提交事务		
session = factory.openSession(); // 手动提交事务

⚠️就编程而言,设置为自动提交方式为 false 再根据情况决定是否进行提交,这种方式更常用。因为我们可以根据业务 情况来决定提交是否进行提交。

Mybatis 的动态 SQL 语句

code

Mybatis 的映射文件中,前面我们的 SQL 都是比较简单的,有些时候业务逻辑复杂时,我们的 SQL 是动态变化的,此时在前面的学习中我们的 SQL 就不能满足要求了。

<if>标签

我们根据实体类的不同取值,使用不同的 SQL 语句来进行查询。比如在 id 如果不为空时可以根据 id 查询,如果 username 不同空时还要加入用户名作为条件。

持久层Dao映射配置:

  • <if>标签的 test 属性中写的是对象的属性名,如果是包装类的对象要使用 OGNL 表达式的写法。
  • 在查询条件数量不确定的条件情况下,使用 where 1=1可以很方便的规范语句;只是为了满足多条件查询页面中不确定的各种因素而采用的一种构造一条正确能运行的动态SQL语句的一种方法。
@Select({"<script> " +
  "select * from user where  1=1 " +
  "<if test='username!=null'> and username = #{username}</if> " +
  "<if test='sex!=null'> and sex = #{sex}</if> " +
  "</script>"})
List<User> findUserByCondition(User user);

<where>标签

为了简化上面where 1=1的条件拼装,我们可以采用标签来简化开发。

@Select({"<script> " +
  "select * from user " +
  "<where>"+
  "<if test='username!=null'> and username = #{username}</if> " +
  "<if test='sex!=null'> and sex = #{sex}</if> " +
  "</where>"+
  "</script>"})
List<User> findUserByCondition(User user);

<foreach>标签

传入多个 id 查询用户信息,用下边两个 sql 实现:

  • SELECT * FROM USERS WHERE username LIKE '%张%' AND (id =10 OR id =89 OR id=16)
  • SELECT * FROM USERS WHERE username LIKE '%张%' AND id IN (10,89,16)

这样我们在进行范围查询时,就要将一个集合中的值,作为参数动态添加进来。

在 QueryVo 中加入一个 List 集合用于封装参数:

SQL 语句:select 字段 from user where id in (?)

<foreach>标签用于遍历集合,它的属性:

  • collection:代表要遍历的集合元素,注意编写时不要写#{}
  • open:代表语句的开始部分
  • close:代表结束部分
  • item:代表遍历集合的每个元素,生成的变量名
  • sperator:代表分隔符
/**
  * @description: 根据queryvo中提供的id集合,查询用户信息
  */
@Select({"<script> " +
  "select * from user " +
  "<where>" +
  "<if test='ids!=null and ids.size()>0'>" +
  "<foreach collection='ids' open='and id in (' close=')' item='id' separator=','> " +
  "#{id}" +
  " </foreach>" +
  "</if>" +
  "</where>"  +
  "</script>"})
List<User> findUserInIds(QueryVo vo);

编写测试方法

@Test
public void testFindInIds(){
  QueryVo vo = new QueryVo();
  List<Integer> list = new ArrayList<>();
  list.add(41);
  list.add(42);
  list.add(43);
  vo.setIds(list);

  List<User> users = userDao.findUserInIds(vo);
  for (User user : users) {
    System.out.println(user);
  }

}

Mybatis中的多表查询

一对多示例:用户和账户

一对多

  • 一个用户可以有多个账户

  • 一个账户只能属于一个用户(多个账户也可以属于一个用户)

步骤:

  1. 建立两张表:用户表,账户表,让用户表和账户表之间具备一对多的关系(需要使用外键在账户表添加)
  2. 建立两个实体类:用户实体类和账户实体类,让用户和实体类能够体现出一对多的关系
  3. 建立两个配置文件
    • 用户的配置文件
    • 账户的配置文件
  4. 实现配置:
    • 当我们查询用户时,可以同时得到用户下包含的账户信息
    • 当我们查询账户时,可以同时得到账户的所属用户信息

多对多示例:用户和角色

多对多

  • 一个用户可以有多个角色
  • 一个角色可以赋予多个用户

步骤:

  1. 建立两张表:用户表,角色表,让用户表和角色表之间具备多对多的关系。(需要使用中间表,中间表包含各自的主键,在中间表中是外键)
  2. 建立两个实体类:用户实体类和角色实体类,让用户和角色类能够体现出多对多的关系(各自包含对方一个集合引用)
  3. 建立两个配置文件
    • 用户的配置文件
    • 角色的配置文件
  4. 实现配置:
    • 当我们查询用户时,可以同时得到用户下包含的角色信息
    • 当我们查询角色时,可以同时得到角色所赋予的用户

Mybatis中的延迟加载

延迟加载

何为延迟加载

实际开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的账户信息。此时就是我们所说的延迟加载。

问题:在一对多种,当我们有一个用户,他有100个账户
	在查询用户的时候,要不要把关联的账户查出来?
	在查询账户的时候,要不要把关联的用户查出来?
	
	在查询用户时,用户下的账户信息应该什么时候使用,什么时候查询出来
	在查询账户时,账户所属的用户信息应该是随着账户查询时一起查询出来的。

延迟加载与立即加载

  • 在真正使用数据时才发起查询,不用的时候不查询。按需加载(懒加载)
  • 好处:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。

什么是立即加载

  • 不管用不用,只要一调用方法马上发起查询

在对应的四种表关系中:

  • 一对多,多对多 :通常情况下采用延迟加载 @One
  • 多对一,一对一:同情情况下采用立即加载 @Many

使用 assocation 实现延迟加载

在 Mybatis 的配置文件 SqlMapConfig.xml 文件中添加延迟加载的配置

<!--配置参数-->
<settings>
  <!--开启Mybatis支持延迟加载-->
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="aggressiveLazyLoading" value="false"></setting>
</settings>

使用 Collection 实现延迟加载

同样我们也可以在一对多关系配置的<collection>结点中配置延迟加载策略。

<collection>结点中也有 select 属性,column 属性。

1)编写用户持久层映射配置

  • <collection>标签:主要用于加载关联的集合对象
  • select 属性:用于指定查询 account 列表的 sql 语句,所以填写的是该 sql 映射的 id
  • column 属性:用于指定 select 属性的 sql 语句的参数来源,上面的参数来自于 user 的 id 列,所以就写成 id 这一个字段名了
<!-- 定义User的resultMap-->
<resultMap id="userAccountMap" type="user">
  <id property="id" column="id"></id>
  <result property="username" column="username"></result>
  <result property="address" column="address"></result>
  <result property="sex" column="sex"></result>
  <result property="birthday" column="birthday"></result>
  <!-- 配置user对象中accounts集合的映射 -->
  <collection property="accounts" ofType="account" select="com.itheima.dao.IAccountDao.findAccountByUid" column="id"></collection>
</resultMap>

2)编写账户持久层映射配置

<!-- 根据id查询用户 -->
<select id="findById" parameterType="INT" resultType="user">
  select * from user where id = #{uid}
</select>

Mybatis缓存

Mybatis中的缓存

  1. 什么是缓存:存在内存中的临时数据
  2. 为什么使用缓存:减少和数据库的交互次数,提高执行效率。
  3. 什么样的数据能使用缓存,什么要的数据不能?
    • 适用于缓存:经常查询并且不经常改变的,数据的正确与否对最终结果影响不大的,
    • 不适用于缓存:经常改变的数据,数据的正确与否对最终结果影响很大(商品的库存,银行的汇率等)
  4. Myabits中的一级缓存和二级缓存
    • 一级缓存:
      • 指的是Mybatis中SqlSession对象的缓存。当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供的一块区域中。
      • 该区域的结构是一个Map,但我们再次查询同样的数据,mybatis会想去sqlsession中查询是否存在,有的话直接使用。
      • 当SqlSession对象消失时,mybatis的一级缓存也就消失了。

    image-20201026195136085

    • 二级缓存:
      • 指的是Mybatis中SqlSessionFactory对象的缓存。
      • 由同一个SqlSessionFactory对象创建的SqlSession共享其缓存
      • ⚠️针对每次查询都需要最新的数据 sql,要设置成 useCache=false,禁用二级缓存。

      image-20201026195218060

      • 存放的内容是数据,而不是对象。

      • 使用步骤:

        1. 让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置)

          <settings>
            <!-- 开启二级缓存的支持 -->
            <setting name="cacheEnabled" value="true"/>
          </settings>
          
        2. 让当前的映射文件支持二级缓存(在IUserDao.xml中配置)

          <!-- <cache>标签表示当前这个 mapper 映射将使用二级缓存,区分的标准就看 mapper 的 namespace 值。 -->
          <mapper namespace="com.itheima.dao.IUserDao">
            <!-- 开启二级缓存的支持 -->
            <cache></cache>
          </mapper>
          
        3. 让当前的操作支持二级缓存(在select标签中配置)

          <!-- 根据 id 查询 -->
          <select id="findById" resultType="user" parameterType="int" useCache="true"> 		select * from user where id = #{uid}
          </select>
          

Mybatis中的注解开发

Mybatis注解开发

mybatis 的常用注解说明

  • @Insert:实现新增
  • @Update:实现更新
  • @Delete:实现删除
  • @Select:实现查询
  • @Result:实现结果集封装
  • @Results:可以与@Result 一起使用,封装多个结果集
  • @ResultMap:实现引用@Results 定义的封装
  • @One:实现一对一结果集封装
  • @Many:实现一对多结果集封装
  • @SelectProvider: 实现动态SQL映射
  • @CacheNamespace:实现注解二级缓存的使用
  • @Param
    • 在方法参数的前面写上@Param(“参数名”),表示给参数命名,名称就是括号中的内容
    • public Student select(@Param("aaaa") String name,@Param("bbbb")int class_id);
    • 给入参 String name 命名为aaaa,然后sql语句....where s_name= #{aaaa} 中就可以根据aaaa得到参数值了。

使用 Mybatis 注解实现基本 CRUD

1)编写实体类 此处我们故意和数据库表的列名不一致。

@Data
public class User implements Serializable{

    private Integer userId;
    private String userName;
    private String userAddress;
    private String userSex;
    private Date userBirthday;

    //一对多关系映射:一个用户对应多个账户
    private List<Account> accounts;
}

2)使用注解方式开发持久层接口

通过注解方式,我们就不需要再去编写 UserDao.xml 映射文件了

//开启二级缓存
@CacheNamespace(blocking = true)
public interface IUserDao {

    /**
     * 查询所有用户
     * @return
     */
    @Select("select * from user")
    @Results(id="userMap",value={
            @Result(id=true,column = "id",property = "userId"),
            @Result(column = "username",property = "userName"),
            @Result(column = "address",property = "userAddress"),
            @Result(column = "sex",property = "userSex"),
            @Result(column = "birthday",property = "userBirthday"),
            @Result(column = "id",property = "accounts",
                    many = @Many(select = "com.itheima.dao.IAccountDao.findAccountByUid",
                                fetchType = FetchType.LAZY))
    })
    List<User> findAll();

    /**
     * 根据id查询用户
     * @param userId
     * @return
     */
    @Select("select * from user  where id=#{id} ")
    @ResultMap("userMap")
    User findById(Integer userId);

    /**
     * 根据用户名称模糊查询
     * @param username
     * @return
     */
    @Select("select * from user where username like #{username} ")
    @ResultMap("userMap")
    List<User> findUserByName(String username);

  	/**
		* 保存操作
		*/
   @Insert("insert into
          user(username,sex,birthday,address)values(#{username},#{sex},#{birthday},#{address} )")
   @SelectKey(keyColumn="id",keyProperty="id",resultType=Integer.class,before = false, statement = { "select last_insert_id()" })
   int saveUser(User user);
           
    /**
    * @description: 根据id删除用户
    */
    @Delete("delete from user where id=#{id}")
    void deleteUser(Integer userId);
  
     /**
     * @description: 查询总用户数
     */
    @Select("select count(id) from user")
    int findTotal();

    /**
    * @description: 更新用户
    */
    @Update("update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id}")
    void updateUser(User user);
}

3)编写 SqlMapConfig 配置文件

<?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="jdbcConfig.properties"></properties>
    <!--配置开启二级缓存-->
    <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>

    <!--配置别名-->
    <typeAliases>
        <package name="com.itheima.domain"></package>
    </typeAliases>
    <!-- 配置环境-->
    <environments default="mysql">
        <environment id="mysql">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"></property>
                <property name="url" value="${jdbc.url}"></property>
                <property name="username" value="${jdbc.username}"></property>
                <property name="password" value="${jdbc.password}"></property>
            </dataSource>
        </environment>
    </environments>
    <!-- 指定带有注解的dao接口所在位置 -->
    <mappers>
       <package name="com.itheima.dao"></package>
    </mappers>
</configuration>

使用注解实现复杂关系映射开发

实现复杂关系映射之前我们可以在映射文件中通过配置<resultMap>来实现,在使用注解开发时我们需要借助@Results 注解,@Result 注解,@One 注解,@Many 注解。

复杂关系映射的注解说明

image-20201026200952201

使用注解实现一对一复杂关系映射及延迟加载

加载账户信息时并且加载该账户的用户信息,根据情况可实现延迟加载。(注解方式实现)

public interface IAccountDao {  
		 /**
     * 查询所有账户,并且获取每个账户所属的用户信息 id=true 表示id为主键  fetchType= FetchType.EAGER 立即加载
     */
    @Select("select * from account")
    @Results(id="accountMap",value = {
            @Result(id=true,column = "id",property = "id"),
            @Result(column = "uid",property = "uid"),
            @Result(column = "money",property = "money"),
            @Result(property = "user",column = "uid",one=@One(select="com.itheima.dao.IUserDao.findById",fetchType= FetchType.EAGER))
    })
    List<Account> findAll();
}

使用注解实现一对多复杂关系映射

查询用户信息时,也要查询他的账户列表。使用注解方式实现。

一个用户具有多个账户信息,所以形成了用户(User)与账户(Account)之间的一对多关系。

@Many:

  • 相当于<collection>的配置
  • select 属性:代表将要执行的 sql 语句
  • fetchType 属性:代表加载方式,一般如果要延迟加载都设置为 LAZY 的值
//开启二级缓存
@CacheNamespace(blocking = true)
public interface IUserDao {

  	 /**
     * 查询所有用户
     * @return
     */
  @Select("select * from user")
  @Results(id="userMap",value={
    @Result(id=true,column = "id",property = "userId"),
    @Result(column = "username",property = "userName"),
    @Result(column = "address",property = "userAddress"),
    @Result(column = "sex",property = "userSex"),
    @Result(column = "birthday",property = "userBirthday"),
    @Result(column = "id",property = "accounts",
            many = @Many(select = "com.itheima.dao.IAccountDao.findAccountByUid",
                         fetchType = FetchType.LAZY))
  })
  List<User> findAll();
}

mybatis 基于注解的二级缓存

@CacheNamespace(blocking=true)//mybatis 基于注解方式实现配置二级缓存 
public interface IUserDao {}