SSM_MyBatis_Day02(多表查询、注解开发、延迟加载策略、缓存)
1. MyBatis
1.1.内容回顾
持久层 ORM
使用步骤
1 引入所需的依赖
2 创建对应的实体
3 编写数据操作接口
4 编写 mybatis的主配置文件 :mybatis运行环境 数据源 事务的支持
5 编写映射文件 sql
6 测试
mybatis的核心配置
mybatis运行环境 数据源 事务的支持 属性文件的引入 别名
动态sql:
- if
- foreach
- choose when otherwise
- where
- sql include
mybatis的dao的实现原理:动态代理
1 在映射文件中 namespace 的值 必须和接口的全类名一致
2 sql的id 必须和方法名称一致
3 映射文件中sql的参数类型必须和方法中的参数类型一致
4 映射文件中sql的resultType 必须和方法的返回值类型一致
字段名和属性名不一致的处理方式 :
1 sql中使用别名
2使用resultmap进行映射
下划线和驼峰命名的转换
1.2.分页查询
物理分页(真分页):就指的是使用真正的sql语句进行分页
逻辑分页(假分页):在执行sql查询的时候 将数据全部查询出来 放在内存的集合 然后针对集合所做的分页(每次取集合中的一定数量的元素)
使用pageHelp
1 引入依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
2 在 MyBatis 配置 xml 中配置拦截器插件
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- helperDialect:分页插件会自动检测当前的数据库链接,自动选择合适的分页方式。 你可以配置helperDialect属性来指定分页插件使用哪种方言。配置时,可以使用下面的缩写值:
oracle,mysql,mariadb,sqlite,hsqldb,postgresql,db2,sqlserver,informix,h2,sqlserver2012,derby-->
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
-
如何在代码中使用
//第二种,Mapper接口方式的调用,推荐这种使用方式。 PageHelper.startPage(1, 10); List<Country> list = countryMapper.selectIf(1);
查看分页的相关数据:
总页数
总记录数
当前页
每页的大小
@Test
public void selelctAllTest() throws IOException {
//设置分页的参数 这个设置必须在执行查询之前设置
PageHelper.startPage(2,5);
List<Stu> stuList = mapper.selectAll();
//pageInfo就封装了所有的分页信息
PageInfo info = new PageInfo<>(stuList);
int pages = info.getPages();
System.out.println("pages = " + pages);
int pageNum = info.getPageNum();
System.out.println("pageNum = " + pageNum);
int pageSize = info.getPageSize();
System.out.println("pageSize = " + pageSize);
long total = info.getTotal();
System.out.println("total = " + total);
int nextPage = info.getNextPage();
System.out.println("nextPage = " + nextPage);
List<Stu> list = info.getList();
for(Stu stu : list){
System.out.println(stu);
}
}
idea的mybatis的插件 可以实现映射文件和方法之间的相互关联跳转
2.mybatis的 多表查询
2.1 一对一查询
2.1.1. 一对一查询的模MapperScannerConfigurer
用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户
一对一查询的需求:查询一个订单,与此同时查询出该订单所属的用户
创建数据表
DROP TABLE IF EXISTS `orders`;
CREATE TABLE `orders` (
`oid` int(11) NOT NULL AUTO_INCREMENT,
`order_time` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`total_price` double(10, 2) NULL DEFAULT NULL,
`uid` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`oid`) USING BTREE,
INDEX `fk_order_user`(`uid`) USING BTREE,
CONSTRAINT `fk_order_user` FOREIGN KEY (`uid`) REFERENCES `users` (`uid`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of orders
-- ----------------------------
SET FOREIGN_KEY_CHECKS = 1;
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`birthday` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`uid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of users
-- ----------------------------
SET FOREIGN_KEY_CHECKS = 1;
#查询某一个订单及其所属用户的信息
select * from orders o ,users u where o.uid= u.uid and oid=1;
创建对应的实体
public class Users {
private Integer uid;
private String username;
private String password;
private String birthday;
public class Orders {
private Integer oid;
private String orderTime;
private Double totalPrice;
//设置关联属性 表示该订单所属的用户信息
private Users users;
配置主配置文件
<?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"></properties>
<settings>
<setting name="logImpl" value="LOG4J"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<typeAliases>
<package name="cn.lanqiao.pojo"/>
</typeAliases>
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
<!-- mybatis运行环境的 配置 default指定当前使用的环境-->
<environments default="development">
<environment id="development">
<!-- 事务配置-->
<transactionManager type="JDBC"/>
<!-- 配置数据源-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!-- 配置sql的映射文件 一会再配-->
<mappers>
<package name="cn.lanqiao.mapper"/>
</mappers>
</configuration>
编写接口文件
public interface OrderMapper {
public Order selectOrderAndUserByOid(Integer oid);
}
映射文件
<?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.lanqiao.mapper.OrderMapper">
<resultMap id="orderUser" type="Order">
<id column="oid" property="oid"></id>
<result column="order_time" property="orderTime"></result>
<result column="total_price" property="totalPrice"></result>
<association property="user" javaType="User">
<id column="uid" property="uid"></id>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<result column="birthday" property="birthday"></result>
</association>
</resultMap>
<select id="selectOrderAndUserByOid" resultMap="orderUser">
select oid,order_time,total_price,u.uid ,username,password,birthday
from orders o , users u where o.uid=u.uid and oid=#{oid}
</select>
</mapper>
一对一查询的另一种方式:查询两次
UserMapper.java
public interface UserMapper {
public User selectUserByUid(Integer uid);
}
OrderMapper.java
public interface OrderMapper {
public Order selectOrderAndUserByOid(Integer oid);
}
UserMapper.xml
<select id="selectUserByUid" resultType="User">
select * from users where uid =#{uid}
</select>
OrderMapper.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.lanqiao.mapper.OrderMapper">
<resultMap id="orderUser" type="Order">
<id column="oid" property="oid"></id>
<result column="order_time" property="orderTime"></result>
<result column="total_price" property="totalPrice"></result>
<!-- select 值是需要执行的sql语句 需要加上namespace column 是关联的外键-->
<association property="user" select="cn.lanqiao.mapper.UserMapper.selectUserByUid" column="uid" javaType="User">
<id column="uid" property="uid"></id>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<result column="birthday" property="birthday"></result>
</association>
</resultMap>
<select id="selectOrderAndUserByOid" resultMap="orderUser">
select * from orders where oid=#{oid}
</select>
</mapper>
2.2 一对多查询
2.2.1 一对多查询的模型
用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户
一对多查询的需求:查询一个用户,与此同时查询出该用户具有的订单
#关联查询
select * from users u ,orders o where u.uid =o.uid and u.uid=1
#分别查询
select * from orders where uid = 1;
select * from users where uid = 1
修改实体
public class Order {
private Integer oid;
private String orderTime;
private Double totalPrice;
public class User {
private Integer uid;
private String username;
private String password;
private String birthday;
private Set<Order> orders;
使用关联查询
public User selelctUserAndOrdersByUid(Integer uid);
UserMapper.xml
<resultMap id="userAndOrders" type="User">
<id column="uid" property="uid"></id>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<result column="birthday" property="birthday"></result>
<collection property="orders" ofType="Order">
<id column="oid" property="oid"></id>
<result column="order_time" property="orderTime"></result>
<result property="totalPrice" column="total_price"></result>
</collection>
</resultMap>
<select id="selelctUserAndOrdersByUid" resultMap="userAndOrders">
select * from users u ,orders o where u.uid =o.uid and u.uid=#{uid}
</select>
单独查询
OrderMapper.java
public List<Order> selelctOrdersByUid(Integer uid);
OrderMapper.xml
<select id="selelctOrdersByUid" resultType="Order">
select * from orders where uid=#{uid}
</select>
测试
@Test
public void selectUserByUid(){
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectUserByUid(1);
System.out.println("user = " + user);
}
日志
2.3.多对多
用户表和角色表的关系为,一个用户有多个角色,一个角色被多个用户使用
多对多查询的需求:查询用户同时查询出该用户的所有角色
#从角色角度出发 查询出所有的角色及其又有每一个角色的用户信息
select r.*,u.*
from role r
left JOIN user_role ur on r.rid = ur.rid
LEFT JOIN users u on u.uid = ur.uid WHERE r.rid= 2
新增一个角色实体
public class Role {
private Integer rid;
private String rname;
private List<User> users;
接口
public interface RoleMapper {
public List<Role> selectAll();
}
映射文件
<?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.lanqiao.mapper.RoleMapper">
<resultMap id="roleUserMap" type="Role">
<id column="rid" property="rid"></id>
<result property="rname" column="rname"></result>
<collection property="users" ofType="User">
<id column="uid" property="uid"></id>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<result column="birthday" property="birthday"></result>
</collection>
</resultMap>
<select id="selectAll" resultMap="roleUserMap">
select r.*,u.*
from role r
left JOIN user_role ur on r.rid = ur.rid
LEFT JOIN users u on u.uid = ur.uid
</select>
</mapper>
测试
@Test
public void selectAllRoleTest(){
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
List<Role> roleList = roleMapper.selectAll();
for(Role role : roleList){
System.out.println(role);
}
}
多对多的关系的查询 一般就使用这种关联查询来解决
3. mybatis的注解开发
@Select("select * from users where uid=#{uid}")
public User selectUserByUid(Integer uid);
与select相同功能的还有
@update
@delete
@insert
使用注解开发,则主配置文件中的
<mappers>
<!-- 扫描该类上所有的 注解-->
<!-- <mapper class="cn.lanqiao.mapper.RoleMapper"></mapper>-->
<!-- 扫描包下的所有的类上的注解-->
<package name="cn.lanqiao.mapper"/>
</mappers>
@Select("select * from users where uid=#{uid}")
@Results({@Result( id = true, column = "uid" ,property = "uid"),
@Result(column = "username",property = "username"),
@Result(column = "birthday" ,property = "birthday")})
public User selectUserByUid(Integer uid);
多表关联的注解
一对多关系映射
@Select("select * from users where uid=#{uid}")
@Results({@Result( id = true, column = "uid" ,property = "uid"),
@Result(column = "username",property = "username"),
@Result(column = "birthday" ,property = "birthday"),
@Result(property = "orders", column = "uid" ,javaType = Set.class,
many = @Many(select = "cn.lanqiao.mapper.OrderMapper.selelctOrdersByUid"))
})
public User selectUserByUid(Integer uid);
一对一
@Select("select * from orders where oid = #{oid}")
@Results({@Result(id = true,column = "oid",property = "oid"),
@Result(column = "order_time",property = "orderTime"),
@Result(column = "total_price",property = "totalPrice"),
@Result(property = "user",javaType = User.class, column = "uid",
one=@One(select = "cn.lanqiao.mapper.UserMapper.selectUserByUid"))})
public Order selectOrderAndUserByOid(Integer oid);
4.Mybatis 延迟加载策略
通过前面的学习,我们已经掌握了 Mybatis 中一对一,一对多,多对多关系的配置及实现,可以实现对象的关联查询。实际开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的账户信息。 此时就是我们所说的延迟加载
4.1 何为延迟加载?
延迟加载:就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载.
好处: 先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
坏处:因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。
4.2.延迟加载实现
一对一关系的延迟加载
一对多
和一对一是相同的设置和使用
5.Mybatis 缓存
像大多数的持久化框架一样, Mybatis 也提供了缓存策略,通过缓存策略来减少数据库的查询次数, 从而提高性能。
Mybatis 中缓存分为一级缓存,二级缓存。
5.1.一级缓存
一级缓存是默认开启的
一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就存在。
5.1.1. 一级缓存的分析
一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除, commit(), close()等方法时,就会清空一级缓存
@Test
public void selectTest(){
orderMapper = sqlSession.getMapper(OrderMapper.class);
Order order = orderMapper.selectOrderAndUserByOid(1);
System.out.println(order.getOrderTime()+"--"+order.getTotalPrice());
sqlSession.clearCache();//在两次执行查询之间 如果情况了缓存 或者执行了增删改 commit的事务相关的操作 缓存都会被清空
order = orderMapper.selectOrderAndUserByOid(1);
System.out.println(order.getOrderTime()+"--"+order.getTotalPrice());
}
5.2.二级缓存
二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。
5.2.1. 二级缓存的开启与关闭
5.2.2.1 第一步:在 SqlMapConfig.xml 文件开启二级缓存
<setting name="cacheEnabled" value="true"/>
因为 cacheEnabled 的取值默认就为 true,所以这一步可以省略不配置。为 true 代表开启二级缓存;为false 代表不开启二级缓存。
5.2.2.2. 第二步:配置相关的 Mapper 映射文件
标签表示当前这个 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.mb.dao.IUserDao">
<!-- 开启二级缓存的支持 -->
<cache></cache>
</mapper>
使用注解开启二级缓存
配置 statement 上面的 useCache 属性
<!-- 根据 id 查询 -->
<select id="findById" resultType="user" parameterType="int" useCache="true">
select * from user where id = #{uid}
</select>
将 UserDao.xml 映射文件中的标签中设置 useCache=”true”代表当前这个 statement 要使用二级缓存,如果不使用二级缓存可以设置为 false。
注意: 针对每次查询都需要最新的数据 sql,要设置成 useCache=false,禁用二级缓存。
二级缓存注意事项
当我们在使用二级缓存时,所缓存的类一定要实现 java.io.Serializable 接口,这种就可以使用序列化方式来保存对象。
5.3.配置第三方缓存
采用 ehcache 来实现 mybatis 的二级缓存
ehcache是一个第三方的专门 用来做缓存的一个缓存框架
引入依赖
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.6</version>
</dependency>
<!-- mybtatis整合ehcache的一个jar-->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
<!-- ehcache 日志依赖 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
<scope>test</scope>
</dependency>
编写ehcache的配置文件****ehcache.xml
<?xml version="1.0" encoding="UTF-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!--
java.io.tmpdir - Default temp file path 默认的 temp 文件目录
maxElementsInMemory:内存中最大缓存对象数.
maxElementsOnDisk:磁盘中最大缓存对象数,若是0表示无穷大.
eternal:Element是否永久有效,一但设置了,timeout将不起作用.
overflowToDisk:配置此属性,当内存中Element数量达到maxElementsInMemory时,Ehcache将会Element写到磁盘中
timeToIdleSeconds:设置Element在失效前的允许闲置时间。仅当element不是永久有效时使用,可选属性,默认值是0, 也就是可闲置时间无穷大
timeToLiveSeconds:设置Element在失效前允许存活时间。最大时间介于创建时间和失效时间之间。仅当element不是永久有效时使用,
默认是0.也就是element存活时间无穷大.
diskPersistent:是否缓存虚拟机重启期数据。(这个虚拟机是指什么虚拟机一直没看明白是什么,有高人还希望能指点一二)
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区.
-->
<diskStore path="E:\ehcache" />
<defaultCache maxElementsInMemory="10000" eternal="false"
timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true"
maxElementsOnDisk="10000000" diskPersistent="false"
diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" />
</ehcache>
在SQL映射文件中配置
<!-- 开启二级缓存的支持 type指定cache的具体实现 -->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
注解
此时使用的缓存框架就是ehcache