MyBatis框架

Mybatis是什么?

  • Mybatis是一个优秀的基于java的持久层框架,它内部封装了 jdbc,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。
  • mybatis 通过 xml 或注解的方式将要执行的各种 statement 配置起来,并通过 java 对象和 statement 中sql 的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。
  • 采用 ORM 思想解决了实体和数据库映射的问题,对 jdbc 进行了封装,屏蔽了 jdbc api 底层访问细节,使我们不用与 jdbc api 打交道,就可以完成对数据库的持久化操作。
  • 为了我们能够更好掌握框架运行的内部过程,并且有更好的体验,下面我们将从自定义 Mybatis 框架开始来学习框架。此时我们将会体验框架从无到有的过程体验,也能够很好的综合前面阶段所学的基础。

一、搭建Mybatis开发环境

1、创建maven工程

2、添加Mybatis坐标

 <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>
 </dependencies>

3、编写实体类User类

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

4、编写持久层接口IUserDao

public interface IUserDao {
/**
 * 查询所有用户
 * @return
*/
	List<User> findAll();
	User findById(Integer userId);
	int saveUser(User user);
	int updateUser(User user);
	int deleteUser(Integer userId);
	List<User> findByName(String username);
	int findTotal();
}

5、编写持久层接口映射文件IUserDao.xml

  • 创建位置:resource目录下,必须和持久层接口在相同的包中。
  • 名称:必须以持久层接口名称命名文件名,扩展名是.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="com.itheima.dao.IUserDao">
	<!-- 配置查询所有操作 --> 
	<select id="findAll" resultType="com.itheima.domain.User">
		select * from user
	</select>
	<!-- 根据 id 查询 , resultMapType:用于指定结果集的类型 , parameterType :用于指定传入参数的类型  -->
	<select id="findById" resultType="com.itheima.domain.User" parameterType="int">
		select * from user where id = #{uid}
	</select>
	<!--  parameterType :传入类的对象,要写类的全限定类名 #{user.username}它会先去找 user 对象,然后在 user 对象中找到 username 属性,并调用getUsername()方法把值取出来。但是我们在 parameterType 属性上指定了实体类名称,所以可以省略 user.而直接写 username。
	 -->
	<insert id="saveUser" parameterType="com.itheima.domain.User" >
		<!-- 配置保存时获取插入的id -->
		<selectKey keyColumn="id" keyProperty="id" resultType="int">
			select last_insert_id();
		</selectKey>
		insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})
	</insert>
	<!-- 更新用户 -->
	<update id="updateUser" parameterType="com.itheima.domain.User">
		update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}
	</update>
	<!-- 删除用户 -->
	<delete id="deleteUser" parameterType="java.lang.Integer">
		delete from user where id = #{uid}
	</delete>
	<!-- 根据名称模糊查询 -->
	<select id="findByName" resultType="com.itheima.domain.User" parameterType="String">
		 select * from user where username like #{username}
	</select>
	<!-- 查询总记录条数 -->
	<select id="findTotal" resultType="int">
		select count(*) from user;
	</select>
</mapper>

6、编写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>
	<!-- 配置 mybatis 的环境 --> 
	<environments default="mysql">
		<!-- 配置 mysql 的环境 --> 
		<environment id="mysql">
			<!-- 配置事务的类型 -->
			<transactionManager type="JDBC"></transactionManager>
			<!-- 配置连接数据库的信息:用的是数据源(连接池) -->
			<dataSource type="POOLED"> 
				<property name="driver" value="com.mysql.jdbc.Driver"/>
				<property name="url" value="jdbc:mysql://localhost:3306/ee50"/>
				<property name="username" value="root"/>
				<property name="password" value="1234"/>
			</dataSource>
		</environment>
	</environments>
	<!-- 告知 mybatis 映射配置的位置 --> 
	<mappers> 
		<mapper resource="com/itheima/dao/IUserDao.xml"/>
	</mappers>
</configuration>

7、编写测试类

public class MybatisTest {
	private InputStream in ;
	private SqlSessionFactory factory;
	private SqlSession session;
	private IUserDao userDao;

	@Before//在测试方法执行之前执行
	public void init()throws Exception {
		//1.读取配置文件
		in = Resources.getResourceAsStream("SqlMapConfig.xml");
		//2.创建构建者对象
		SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
		//3.创建 SqlSession 工厂对象
		factory = builder.build(in);
		//4.创建 SqlSession 对象
		session = factory.openSession();
		//5.创建 Dao 的代理对象
		userDao = session.getMapper(IUserDao.class);
	}
	@After//在测试方法执行完成之后执行
	public void destroy() throws Exception{
		session.commit();
		//7.释放资源
		session.close();
		in.close();
	}
	@Test
	public void testFindOne() {
		//6.执行操作
		User user = userDao.findById(41);
		System.out.println(user);
	}
	@Test
	public void testFindOne() {
		//6.执行操作
		List<User> users = userDao.findAll();
		for(User user : users) {
			System.out.println(user);
		}
	}
	@Test
	public void testSave(){
		User user = new User();
		user.setUsername("modify User property");
		user.setAddress("北京市顺义区");
		user.setSex("男");
		user.setBirthday(new Date());
		System.out.println("保存操作之前:"+user);
		//5.执行保存方法
		userDao.saveUser(user);
		System.out.println("保存操作之后:"+user);
	}
	@Test
	public void testUpdateUser()throws Exception{
		//1.根据 id 查询
		User user = userDao.findById(52);
		//2.更新操作
		user.setAddress("北京市顺义区");
		int res = userDao.updateUser(user);
		System.out.println(res);
	}
	@Test
	public void testDeleteUser() throws Exception {
		//6.执行操作
		int res = userDao.deleteUser(52);
		System.out.println(res);
	}
	@Test
	public void testFindByName(){
		//5.执行查询一个方法
		//我们在配置文件中没有加入%来作为模糊查询的条件,所以在传入字符串实参时,就需要给定模糊查询的标识%。
		List<User> users = userDao.findByName("%王%");
		for(User user : users){
			System.out.println(user);
		}
	}
	@Test
	public void testFindTotal() throws Exception {
		//6.执行操作
		int res = userDao.findTotal();
		System.out.println(res);
	}
} 

8、基于注解的Mybatis

在持久层接口中添加注解

public interface IUserDao {
	/**
	* 查询所有用户
	* @return
	*/
	@Select("select * from user")
	List<User> findAll();
}

修改SqlMapConfig.xml
注意:在使用基于注解的 Mybatis 配置时,请移除 xml 的映射配置(IUserDao.xml)

<!-- 告知 mybatis 映射配置的位置 --> 
<mappers> 
	<mapper class="com.itheima.dao.IUserDao"/>
</mappers>

9、Mybatis流程

Mybatis流程

二、Mybaits参数深入

1、parameterType配置参数

  • SQL 语句传参,使用标签的 parameterType 属性来设定。该属性的取值可以
    是基本类型,引用类型(例如:String 类型),还可以是实体类类型(POJO 类)。
  • 基 本 类 型 和 String 我 们 可 以 直 接 写 类 型 名 称 , 也 可 以 使 用 包 名 . 类 名 的 方 式 , 例 如 :java.lang.String。实体类类型,目前我们只能使用全限定类名。

2、resultType配置结果类型

  • resultType 属性可以指定结果集的类型,它支持基本类型和实体类类型。
  • 需要注意的是,它和 parameterType 一样,如果注册过类型别名的,可以直接使用别名。没有注册过的必须使用全限定类名。
  • 实体类中的属性名称必须和查询语句中的列名保持一致,否则无法实现封装。

3、resultMap结果类型

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

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

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

2、properties(属性)

  1. 第一种方式:
<properties>
	<property name="jdbc.driver" value="com.mysql.jdbc.Driver"/>
	<property name="jdbc.url" value="jdbc:mysql://localhost:3306/xxx"/>
	<property name="jdbc.username" value="root"/>
	<property name="jdbc.password" value="xxxxxx"/>
</properties>
  • 第二种方式:
    在 classpath 下定义 db.properties 文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/eesy
jdbc.username=root
jdbc.password=1234

properties 标签配置

<!-- 配置连接数据库的信息
resource 属性:用于指定 properties 配置文件的位置,要求配置文件必须在类路径下
resource="jdbcConfig.properties"
url 属性:
URL: Uniform Resource Locator 统一资源定位符
http://localhost:8080/mystroe/CategoryServlet URL
协议 主机 端口 URI
URI:Uniform Resource Identifier 统一资源标识符
/mystroe/CategoryServlet
它是可以在 web 应用中唯一定位一个资源的路径
-->
<properties url=file:///D:/IdeaProjects/day02_eesy_01mybatisCRUD/src/main/resources/jdbcConfig.properties">
	<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>
</properties>

3、typeAliases(类型别名)

在 SqlMapConfig.xml 中配置:

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

4、mappers(映射器)

使用相对于类路径的资源

<mapper resource="com/hello/dao/IUserDao.xml"/>

使用 mapper 接口类路径

<!-- 此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。 -->
<mapper class="com.hello.dao.UserDao"/>

注册指定包下的所有 mapper 接口

<!-- 此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。 -->
<package name="com.hello.dao.mapper"/>

四、Mybatis 的动态 SQL 语句

1、<if>标签

  1. 持久层 Dao 接口
/**
* 根据用户信息,查询用户列表
* @param user
* @return
*/
	List<User> findByUser(User user);
  1. 持久层 Dao 映射配
<select id="findByUser" resultType="user" parameterType="user">
	select * from user where 1=1
	<if test="username!=null and username != '' ">
		and username like #{username}
	</if>
	<if test="address != null">
		and address like #{address}
	</if>
</select>
注意:<if>标签的 test 属性中写的是对象的属性名,如果是包装类的对象要使用 OGNL 表达式的写法。另外要注意 where 1=1 的作用~
  1. 测试
@Test
public void testFindByUser() {
	User u = new User();
	u.setUsername("%王%");
	u.setAddress("%顺义%");
	//6.执行操作
	List<User> users = userDao.findByUser(u);
	for(User user : users) {
		System.out.println(user);
	}
}

2、< where > 标签

<!-- 根据用户信息查询 -->
<select id="findByUser" resultType="user" parameterType="user">
	<include refid="defaultSql"></include>
	<where>
		<if test="username!=null and username != '' ">
			and username like #{username}
		</if>
		<if test="address != null">
			and address like #{address}
		</if>
	</where>
</select>

3、< foreach > 标签

  1. 在 QueryVo 中加入一个 List 集合用于封装参数
/**
*
* <p>Title: QueryVo</p>
* <p>Description: 查询的条件</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class QueryVo implements Serializable {
	private List<Integer> ids;
	public List<Integer> getIds() {
		return ids;
	}
	public void setIds(List<Integer> ids) {
		this.ids = ids;
	}
}
  1. 持久层 Dao 接口
/**
* 根据 id 集合查询用户
* @param vo
* @return
*/
	List<User> findInIds(QueryVo vo);
  1. 持久层 Dao 映射配置
<!-- 查询所有用户在 id 的集合之中 -->
<!-- 抽取重复的语句代码片段 -->
<sql id="defaultSql">
	select * from user
</sql>
<select id="findInIds" resultType="user" parameterType="queryvo">
	<!-- select * from user where id in (1,2,3,4,5); -->
	<include refid="defaultSql"></include>
	<where>
		<if test="ids != null and ids.size() > 0">
			<foreach collection="ids" open="id in ( " close=")" item="uid" separator=",">
				#{uid}
			</foreach>
		</if>
	</where>
</select>
SQL 语句:
select 字段 from user where id in (?)
<foreach>标签用于遍历集合,它的属性:
collection:代表要遍历的集合元素,注意编写时不要写#{}
open:代表语句的开始部分
close:代表结束部分
item:代表遍历集合的每个元素,生成的变量名
sperator:代表分隔符
item:代表遍历集合的每个元素,生成的变量名
sperator:代表分隔符
  1. 测试
	@Test
	public void testFindInIds() {
		QueryVo vo = new QueryVo();
		List<Integer> ids = new ArrayList<Integer>();
			ids.add(41);
			ids.add(42);
			ids.add(43);
			ids.add(46);
			ids.add(57);
			vo.setIds(ids);
			//6.执行操作
			List<User> users = userDao.findInIds(vo);
			for(User user : users) {
				System.out.println(user);
			}
	}

五、Mybatis 多表查询之一对多

1、一对一查询

需求:查询所有账户信息,关联查询下单用户信息。
注意:因为一个账户信息只能供某个用户使用,所以从查询账户信息出发关联查询用户信息为一对一查询。如 果从用户信息出发查询用户下的账户信息则为一对多查询,因为一个用户可以有多个账户。

(1)、方式一:定义 AccountUser 类(推荐)
  1. 定义账户信息的实体类
@Data
public class Account  {
	private Integer id;
	private Integer uid;
	private Double money;
}
  1. 编写Sql语句
SELECT
 account.*,
 user.username,
 user.address
FROM
 account,
 user
WHERE account.uid = user.id
  1. 定义 AccountUser 类

为了能够封装上面 SQL 语句的查询结果,定义 AccountCustomer 类中要包含账户信息同时还要包含用户信
息,所以我们要在定义 AccountUser 类时可以继承 User 类。

@Data
public class AccountUser extends Account  {
	private String username;
	private String address;
}
  1. 定义账户的持久层 Dao 接口
public interface IAccountDao {
	/**
	* 查询所有账户,同时获取账户的所属用户名称以及它的地址信息
	* @return
	*/
	List<AccountUser> findAll();
}
  1. 定义 AccountDao.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="com.itheima.dao.IAccountDao">
	<!-- 配置查询所有操作-->
	<select id="findAll" resultType="accountuser">
		select a.*,u.username,u.address from account a,user u where a.uid =u.id;
	</select>
</mapper>
注意:因为上面查询的结果中包含了账户信息同时还包含了用户信息,所以我们的返回值类型 returnType
的值设置为 AccountUser 类型,这样就可以接收账户信息和用户信息了。
  1. 创建 AccountTest 测试类
@Test
public void testFindAll() {
	//6.执行操作
	List<AccountUser> accountusers = accountDao.findAll();
	for(AccountUser au : accountusers) {
		System.out.println(au);
	}
}
(2)方式二:使用resultMap映射一对一查询结果
  1. 修改 Account 类,在 Account 类中加入 User 类的对象作为 Account 类的一个属性。
/**
*
* <p>Title: Account</p>
* <p>Description: 账户的实体类</p>
* <p>Company: http://www.itheima.com/ </p>
*/
@Data
public class Account  {
	private Integer id;
	private Integer uid;
	private Double money;
	
	private User user;

}
  1. 修改 AccountDao 接口中的方法
public interface IAccountDao {
	/**
	* 查询所有账户,同时获取账户的所属用户名称以及它的地址信息
	* @return
	*/
	List<Account> findAll();
}
注意:第二种方式,将返回值改 为了 Account 类型。
因为 Account 类中包含了一个 User 类的对象,它可以封装账户所对应的用户信息。
  1. 重新定义 AccountDao.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="com.itheima.dao.IAccountDao">
	<!-- 建立对应关系 -->
	<resultMap type="account" id="accountMap">
		<id column="aid" property="id"/>
		<result column="uid" property="uid"/>
		<result column="money" property="money"/>
		<!-- 它是用于指定从表方的引用实体属性的 -->
		<association property="user" javaType="user">
			<id column="id" property="id"/>
			<result column="username" property="username"/>
			<result column="sex" property="sex"/>
			<result column="birthday" property="birthday"/>
			<result column="address" property="address"/>
		</association>
	</resultMap>
	<select id="findAll" resultMap="accountMap">
		select u.*,a.id as aid,a.uid,a.money from account a,user u where a.uid =u.id;
	</select>
</mapper>
  1. 在 AccountTest 类中加入测试方法
@Test
public void testFindAll() {
	List<Account> accounts = accountDao.findAll();
	for(Account au : accounts) {
		System.out.println(au);
		System.out.println(au.getUser());
	}
}

2、一对多查询

需求:查询所有用户信息及用户关联的账户信息。
分析:用户信息和他的账户信息为一对多关系,并且查询过程中如果用户没有账户信息,此时也要将用户信息查询出来,我们想到了左外连接查询比较合适。

  1. 编写SQL语句
SELECT
u.*, acc.id id,
acc.uid,
 acc.money
FROM
user u
LEFT JOIN account acc ON u.id = acc.uid
  1. User 类加入 List<Account>
@Data
public class User implements Serializable {
	private Integer id;
	private String username;
	private Date birthday;
	private String sex;
	private String address;
	private List<Account> accounts;
}
  1. 用户持久层 Dao 接口中加入查询方法
	/**
	* 查询所有用户,同时获取出每个用户下的所有账户信息
	* @return
	*/
	List<User> findAll();
  1. 用户持久层 Dao 映射文件配置
<?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.itheima.dao.IUserDao">
	<resultMap type="user" id="userMap">
		<id column="id" property="id"></id>
		<result column="username" property="username"/>
		<result column="address" property="address"/>
		<result column="sex" property="sex"/>
		<result column="birthday" property="birthday"/>
		<!-- collection 是用于建立一对多中集合属性的对应关系ofType 用于指定集合元素的数据类型-->
		<collection property="accounts" ofType="account">
			<id column="aid" property="id"/>
			<result column="uid" property="uid"/>
			<result column="money" property="money"/>
		</collection>
	</resultMap>
	<!-- 配置查询所有操作 -->
	<select id="findAll" resultMap="userMap">
		select u.*,a.id as aid ,a.uid,a.money from user u left outer join account a on u.id =a.uid
	</select>
</mapper>
collection: 部分定义了用户关联的账户信息。表示关联查询结果集
property="accList":关联查询的结果集存储在 User 对象的上哪个属性。
ofType="account":指定关联查询的结果集中的对象类型即List中的对象类型。此处可以使用别名,也可以使用全限定名。
  1. 测试方法
@Test
public void testFindAll() {
	//6.执行操作
	List<User> users = userDao.findAll();
	for(User user : users) {
		System.out.println("-------每个用户的内容---------");
		System.out.println(user);
		System.out.println(user.getAccounts());
	}
}

3、多对多查询

实现 Role 到 User 多对多 ;用户表,角色表,角色用户中间表
通过前面的学习,我们使用 Mybatis 实现一对多关系的维护。多对多关系其实我们看成是双向的一对多关
系。
1、SQL

SELECT
 r.*,u.id uid,
 u.username username,
 u.birthday birthday,
 u.sex sex,
 u.address address
FROM
 ROLE r
INNER JOIN
 USER_ROLE ur
ON ( r.id = ur.rid)
INNER JOIN
 USER u
ON (ur.uid = u.id);
  1. 实体类
    角色实体类
public class Role implements Serializable {
	 private Integer roleId;
	 private String roleName;
	 private String roleDesc;
	 //多对多的关系映射:一个角色可以赋予多个用户
	 private List<User> users;
}
  1. 编写 Role 持久层接口
public interface IRoleDao {
 /**
 * 查询所有角色
 * @return
 */
 List<Role> findAll();
}
  1. 编写映射文件
<?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.itheima.dao.IRoleDao">
	 <!--定义 role 表的 ResultMap-->
	 <resultMap id="roleMap" type="role">
		 <id property="roleId" column="rid"></id>
		 <result property="roleName" column="role_name"></result>
		 <result property="roleDesc" column="role_desc"></result>
		 <collection property="users" ofType="user">
			 <id column="id" property="id"></id>
			 <result column="username" property="username"></result>
			 <result column="address" property="address"></result>
			 <result column="sex" property="sex"></result>
			 <result column="birthday" property="birthday"></result>
		 </collection>
	 </resultMap>
	 <!--查询所有-->
	 <select id="findAll" resultMap="roleMap">
		 select u.*,r.id as rid,r.role_name,r.role_desc 
		 from role r
		 left outer join user_role ur on r.id = ur.rid
		 left outer join user u on u.id = ur.uid
	 </select>
</mapper>
  1. 测试类
@Test
 public void testFindAll(){
	 List<Role> roles = roleDao.findAll();
	 for(Role role : roles){
		 System.out.println("---每个角色的信息----");
		 System.out.println(role);
		 System.out.println(role.getUsers());
	 }
 }
  1. 实现 User 到 Role 的多对多

从 User 出发,我们也可以发现一个用户可以具有多个角色,这样用户到角色的关系也还是一对多关系。这样
我们就可以认为 User 与 Role 的多对多关系,可以被拆解成两个一对多关系来实现。

六、延迟加载策略(TODO)

延迟加载:就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载.
好处:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
坏处:因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。

七、Mybatis缓存

1、Mybatis一级缓存

一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就存在。

  1. 编写用户持久层 Dao 接口
public interface IUserDao {
	/**
	* 根据 id 查询
	* @param userId
	* @return
	*/
	User findById(Integer userId);
}
  1. 编写用户持久层映射文件
<?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.itheima.dao.IUserDao">
	<!-- 根据 id 查询 -->
	<select id="findById" resultType="UsEr" parameterType="int" useCache="true">
		select * from user where id = #{uid}
	</select>
</mapper>
  1. 编写测试方法
@Test
public void testFindById() {
	User user = userDao.findById(41);
	System.out.println("第一次查询的用户:"+user);
	User user2 = userDao.findById(41);
	System.out.println("第二次查询用户:"+user2);
	System.out.println(user == user2);
}
  1. 测试结果

我们可以发现,虽然在上面的代码中我们查询了两次,但最后只执行了一次数据库操作,这就是 Mybatis 提
供给我们的一级缓存在起作用了。因为一级缓存的存在,导致第二次查询 id 为 41 的记录时,并没有发出 sql 语句
从数据库中查询数据,而是从一级缓存中查询。

  1. 一级缓存的分析

一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。

在这里插入图片描述

第一次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查
询用户信息。
得到用户信息,将用户信息存储到一级缓存中。
如果 sqlSession 去执行 commit 操作(执行插入、更新、删除),清空 SqlSession 中的一级缓存,这样
做的目的为了让缓存中存储的是最新的信息,避免脏读。
第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存
中获取用户信息。

测试一级缓存的清空

@Test
 public void testFirstLevelCache(){
 User user1 = userDao.findById(41);
 System.out.println(user1);
// sqlSession.close();
 //再次获取 SqlSession 对象
// sqlSession = factory.openSession();
 sqlSession.clearCache();//此方法也可以清空缓存
 userDao = sqlSession.getMapper(IUserDao.class);
 User user2 = userDao.findById(41);
 System.out.println(user2);
 System.out.println(user1 == user2);
 }

2、Mybatis二级缓存

(1)、介绍

二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个
SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。
在这里插入图片描述
首先开启 mybatis 的二级缓存。
sqlSession1 去查询用户信息,查询到用户信息会将查询数据存储到二级缓存中。
如果 SqlSession3 去执行相同 mapper 映射下 sql,执行 commit 提交,将会清空该 mapper 映射下的二
级缓存区域的数据。
sqlSession2 去查询与 sqlSession1 相同的用户信息,首先会去缓存中找是否存在数据,如果存在直接从
缓存中取出数据。

(2)、二级缓存的开启与关闭
  1. 第一步:在 SqlMapConfig.xml 文件开启二级缓存
<settings>
	<!-- 开启二级缓存的支持 -->
	<setting name="cacheEnabled" value="true"/>
</settings>
因为 cacheEnabled 的取值默认就为 true,所以这一步可以省略不配置。为 true 代表开启二级缓存;为
false 代表不开启二级缓存。
  1. 第二步:配置相关的 Mapper 映射文件
<cache>标签表示当前这个 mapper 映射将使用二级缓存,区分的标准就看 mapper 的 namespace 值。
<?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.itheima.dao.IUserDao">
<!-- 开启二级缓存的支持 -->
<cache></cache>
</mapper>
  1. 第三步:配置 statement 上面的 useCache 属性
<!-- 根据 id 查询 -->
<select id="findById" resultType="user" parameterType="int" useCache="true">
select * from user where id = #{uid}
</select>
将 UserDao.xml 映射文件中的<select>标签中设置 useCache=”true”代表当前这个 statement 要使用
二级缓存,如果不使用二级缓存可以设置为 false。
注意:针对每次查询都需要最新的数据 sql,要设置成 useCache=false,禁用二级缓存

4.测试

@Test
 public void testFirstLevelCache(){
 SqlSession sqlSession1 = factory.openSession();
 IUserDao dao1 = sqlSession1.getMapper(IUserDao.class);
 User user1 = dao1.findById(41);
 System.out.println(user1);
 sqlSession1.close();//一级缓存消失
 SqlSession sqlSession2 = factory.openSession();
 IUserDao dao2 = sqlSession2.getMapper(IUserDao.class);
 User user2 = dao2.findById(41);
 System.out.println(user2);
 sqlSession2.close();
 System.out.println(user1 == user2);
 }

注意:

当我们在使用二级缓存时,所缓存的类一定要实现 java.io.Serializable 接口,这种就可以使用序列化
方式来保存对象。
/**
*
* <p>Title: User</p>
* <p>Description: 用户的实体类</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值