Mybatis
代码集合:Myabits
环境:
JDK 19.0.2
Mysql 8.0.33
Maven 3.9.4
IDEA
回顾:
JDBC
Mysql
java基础
maven
Junit单元测试
1. 简介
-
优秀的持久层框架
-
定制化SQL
-
原名iBatis
1.1 如何获得
-
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.13</version> </dependency>
-
1.2 持久化
数据持久化:将程序的数据在持久状态和瞬时状态转化的过程
为什么要持久化:有一些东西不能丢掉,内存贵
1.3 持久层
完成持久化工作的代码块,层界限明显
1.4 为什么需要Mybatis
简化JDBC代码、框架
帮助程序员把数据存入数据库中
2. 第一个Mybatis程序
项目结构如图所示
2.1 搭建环境
搭建数据库
CREATE DATABASE `mybatis`;
USE `mybatis`;
CREATE TABLE `user`(
`id` INT(20) NOT NULL PRIMARY KEY,
`name` VARCHAR(30) DEFAULT NULL,
`pwd` VARCHAR(30) DEFAULT NULL
)`user`ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `user`(`id`,`name`,`pwd`) VALUES
(1,'hahahah','1123456'),
(2,'hahah','112345'),
(3,'hah','11234')
新建项目:创建一个普通的maven项目
*使用jdk 1.8
删除src目录
导入maven依赖:mysql\mybatis\junit
<!--导入依赖--> <dependencies> <!--mysql驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.46</version> </dependency> <!--mybatis--> <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.13</version> </dependency> <!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> </dependency> </dependencies>
2.2 导入Mybatis
-
编写mybatis的核心配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://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.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
</configuration>
-
编写Mybatis工具类
package org.example.utils; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.*; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.IOException; import java.io.InputStream; public class MybatisUtils { //定义工具类 private static SqlSessionFactory sqlSessionFactory; //使用Mybatis第一步:获取sqlSessionFactory对象 static { try{ String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e){ e.printStackTrace(); } } //获得SqlSession实例:包含了面向数据库执行SQL命令所需的所有方法 public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); } }
2.3 编写代码
-
实体类
package org.example.pojo; //lombok:使用注解可以简化一些必须有但又显得臃肿的代码,比如set和get方法,属性较多时,一堆的get和set方法占用很多行,影响阅读性 @Data //实体类 public class User { private int id; private String name; private String pwd; }
-
Dao接口
package mybatis.demo.dao; import mybatis.demo.pojo.User; import java.util.List; public interface UserDao {//Dao等价于之后的Mapper List<User> getUserList(); //查询全部用户 List<User> getUserList(); }
-
接口实现类
package mybatis.demo.dao; import mybatis.demo.pojo.User; import java.util.List; public interface UserDao {//Dao等价于之后的Mapper List<User> getUserList(); //查询全部用户 List<User> getUserList(); }
2.4 测试
注意点:org.apache.ibatis.binding.BindinException:Type interface mybatis.demo.dao.UserDao is not known to the MapperRegistry.【修改之后要reload那个pom.xml文件】
-
junit测试
package org.example.dao; import org.example.utils.MybatisUtils; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import org.example.pojo.User; import java.util.List; public class UserDaoTest { @Test public void test(){ //获得SqlSession对象 SqlSession sqlSession = MybatisUtils.getSqlSession(); //执行getMapper UserDao userDao = sqlSession.getMapper(UserDao.class); List<User> userList = userDao.getUserList(); for(User user : userList){ System.out.println(user); } //关闭SqlSession sqlSession.close(); } }
2.5 CRUD增删改查
2.5.1 namespace
namespace中的包名要和接口(Dao/Mapper)的包名一致
2.5.2 select
选择查询语句:
-
id:对应的namespace中的方法名
-
resultType:Sql语句执行的返回值
-
parameterType:参数类型
需要修改的文件以及代码:
UserMapper.java
package org.example.dao;
import org.example.pojo.User;
import java.util.List;
import java.util.Map;
public interface UserMapper {
List<User> getUserLike(String value);
//查询全部用户
List<User> getUserList();
//根据ID查询用户
User getUserById(int id);
//insert一个用户
int addUser(User user);
//修改用户
int updateUser(User user);
//删除一个用户
int deleteUser(int id);
}
UserMapper.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">
<!--namespace=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="org.example.dao.UserMapper">
<!--
select * from mybatis.user where id = ?
select * from mybatis.user where id = 1 or 1=1
-->
<select id="getUserLike" resultType="org.example.pojo.User">
select * from user where name like "%"#{value}"%"
</select>
<!--select查询语句-->
<select id="getUserList" resultType="org.example.pojo.User">
select * from user
</select>
<select id="getUserById" resultType="org.example.pojo.User">
select * from user where id = #{id}
</select>
<!--对象中的属性,可以直接取出来-->
<insert id="addUser" parameterType="org.example.pojo.User">
insert into user (id, name, pwd) values (#{id},#{name},#{pwd});
</insert>
<update id="updateUser" parameterType="org.example.pojo.User">
update user set name=#{name},pwd=#{pwd} where id = #{id} ;
</update>
<delete id="deleteUser" parameterType="int">
delete from user where id = #{id};
</delete>
</mapper>
UserDaoTest.java
package org.example.dao;
import org.example.pojo.User;
import org.example.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class UserDaoTest {
@Test
public void getUserLike(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserLike("%李%");
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
@Test
public void test() {
//第一步:获得SqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
try {
//方式一:getMapper
UserMapper userDao = sqlSession.getMapper(UserMapper.class);
List<User> userList = userDao.getUserList();
for (User user : userList) {
System.out.println(user);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭SqlSession
sqlSession.close();
}
}
@Test
public void getUserById() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(1);
System.out.println(user);
sqlSession.close();
}
//增删改需要提交事务
@Test
public void addUser() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int res = mapper.addUser(new User(4, "哈哈", "123333"));
if (res > 0) {
System.out.println("插入成功!");
}
//提交事务
sqlSession.commit();
sqlSession.close();
}
@Test
public void updateUser() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.updateUser(new User(4, "呵呵", "123123"));
//提交事务
sqlSession.commit();
sqlSession.close();
}
@Test
public void deleteUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.deleteUser(4);
sqlSession.commit();
sqlSession.close();
}
}
2.6 Map
实体类或者数据库中的表,字段或者参数过多,适当考虑使用Map
Map中传递参数,直接在sql中取出key
对象传递参数,直接在sql中取出对象的属性
只有一个基本类型,直接在sql中取到
//不需要知道数据库里有什么,直接去查询
User addUserById2(Map<String,Object> map);
<!--map中的key大小写之类的都无所谓-->
<insert id="addUser2" parameterType="map">
insert into user(id,name,pwd) values (#{userid},#{userName},#{passwd});
</insert>
public void addUser2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String,Object> map = new Map<String,Object>();
//其他值如果没有变化或者不需要修改都不用添加,相较于之前的方法来说,对于很多个对象来书不需要new很多个选项,只需要改变需要改变的就行
map.put("userid",5);
mapper.addUser2(map);
sqlSession.close();
}
2.7 模糊查询
java代码执行的时候,传递通配符% %
List<User> userList = mapper.getUserLike("%李%");
在sql拼接中使用通配符
select * from user where name like "%"#{value}"%"
List<User> getUserLike(String value);
<select id="getUserLike" resultType="org.example.pojo.User">
select * from user where name like "%"#{value}"%"
</select>
<select id="getUserLike" resultType="org.example.pojo.User">
select * from user where name like "%"#{value}"%"
</select>
3. 配置
3.1 属性优化
3.1.1核心配置文件
mybatis-config.xml
Mybatis的配置文件包含了会深深影响Mybatis行为的设置和属性信息
3.1.2 环境配置
MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。
尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。
-
每个数据库对应一个 SqlSessionFactory 实例
//为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可。可以接受环境配置的两个方法签名是: SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties); //如果忽略了环境参数,那么将会加载默认环境,如下所示: SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, properties);
-
environments 元素定义了如何配置环境
-
默认使用的环境 ID(比如:default="development")。
-
每个 environment 元素定义的环境 ID(比如:id="development")。
-
事务管理器的配置(比如:type="JDBC")。
-
数据源的配置(比如:type="POOLED")。
默认环境和环境 ID 顾名思义。 环境可以随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID。
<environments default="development"> <environment id="development"> <transactionManager type="JDBC"> <property name="..." value="..."/> </transactionManager> <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>
-
3.1.2.1 事务管理器
两种类型的事务管理器:JDBC(mybatis默认的事务管理器)、MANAGED
JDBC:这个配置直接使用了 JDBC 的提交和回滚功能,它依赖从数据源获得的连接来管理事务作用域。默认情况下,为了与某些驱动程序兼容,它在关闭连接时启用自动提交。
将 "skipSetAutoCommitOnClose" 属性设置为 "true" 来跳过启用自动提交
<transactionManager type="JDBC"> <property name="skipSetAutoCommitOnClose" value="true"/> </transactionManager>
MANAGED:这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。
<transactionManager type="MANAGED"> <property name="closeConnection" value="false"/> </transactionManager>
3.1.2.2 数据源
连接数据库,mybatis默认的连接池POOLED
3.1.3 属性
通过属性引用配置文件
这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
修改01代码为02:
编写一个配置文件db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
pwd=root
在核心配置文件中引入:xml中的标签有顺序,properties只能在最上面
<!--引入外部配置文件-->
<properties resource="db.properties">
<!--在这里也可以增加属性配置,优先使用外部配置文件db.properties中的信息-->
<property name="username" value="root"/>
<property name="pwd" value="11111"/>
</properties>
3.2 别名优化
类型别名可为 Java 类型设置一个缩写名字。
存在的意义仅在于用来减少类完全限定名的荣誉
<!--可以给实体类起别名-->
<typeAliases>
<package name="com.kuang.pojo.User" alias="User"/>
</typeAliases>
也可以指定一个包名,Mybatis会在包名下面搜索需要的Java Bean
扫描实体类的包,它的默认别名就是这个类的类名,首字母小写
<!--可以给实体类起别名-->
<typeAliases>
<package name="com.kuang.pojo"/>
</typeAliases>
在实体类比较少的时候使用第一种方式,如果实体类多使用第二种方式。第一种可以自定义别名,第二种可以加注解@alias
@alias("user") public class User{}
3.3 设置
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。
3.4 其他配置
-
plugins插件
-
mybatis-generator-core
-
mybatis-plus
-
通用mapper
-
-
映射器
MapperRegistry:注册绑定Mapper文件
方式1:
<!--每一个Mapper.XML都需要在Mybatis核心配置文件中注册!-->
<mappers>
<mapper resource="com/kuang/dao/UserMapper.xml"/>
</mappers>
方式2:通过class绑定文件注册
接口和mapper配置文件必须同名(在一个包下)
<!--每一个Mapper.XML都需要在Mybatis核心配置文件中注册!-->
<mappers>
<mapper class="com.kuang.dao.UserMapperper/>
</mappers>
方式3:通过package绑定文件注册
必须同名,在同一包下
<!--每一个Mapper.XML都需要在Mybatis核心配置文件中注册!-->
<mappers>
<package name="com.kuang.dao"/>
</mappers>
3.5 生命周期和作用域
错误的使用会导致并发问题
SqlSessionFatoryBuilder:
-
一旦创建SqlSessionFactory,就不再需要它了
-
局部变量
SqlSessionFactory:
-
可以想象为数据库的连接池
-
一旦被创建就应该在应用的运行期间一直存在,没有理由丢弃或者重新创建一个实例
-
最佳作用域是应用作用域
-
最简单的是使用单例模式或者静态单例模式
SqlSession:
-
连接到连接池的一个请求
-
实例不是线程安全的,不能被共享,最佳作用域是请求或者方法作用域
-
使用完之后需要关闭,减少资源占用
4. 解决属性名和字段名不一致的问题
新建一个项目,拷贝之前的,测试实体类字段不一致的情况,测试出现问题
// select * from user where id = #{id} //类型处理器 // select id,name,pwd from user where id = #{id}
解决方法:
-
起别名
<select id="getUserById" resultType="com.kuang.pojo.User"> select id,name,pwd as password from user where id = #{id} </select>
4.1 ResultMap结果集映射
id name pwd
id name password
<!--结果集映射-->
<resultMap id="UserMap" type="User">
<!--column数据库中的字段,property实体类中的属性-->
<!--<result column="id" property="id"/>-->
<!--<result column="name" property="name"/>-->
<result column="pwd" property="password"/>
</resultMap>
-
resultMap
元素是Mybatis中最重要最强大的元素 -
ResultMap的设计思想是,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语句只需要描述它们的关系就行了。
5. 日志
5.1 日志工厂
如果一个数据库操作出现了异常,需要排错。
曾经:sout、debug
现在:日志工厂
-
SLF4J
-
LOG4J(3.5.9 起废弃) 【掌握】
-
LOG4J2
-
JDK_LOGGING
-
COMMONS_LOGGING
-
STDOUT_LOGGING 【掌握】
-
NO_LOGGING
具体使用哪一个日志实现,在设置中设定。
STDOUT_LOGGING:标准日志输出
mybatis-config.xml(核心配置文件)
<settings>
<!--标准的日志工厂实现-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
5.2 Log4j
控制日志信息输送到目的地是控制台 、文件、GUI组件
控制每一条日志的输出格式,通过定义每一条日志信息的级别,控制日志的生成过程
通过一个配置文件进行配置,不需要修改应用的代码
导入Log4j的包
pom.xml
<dependencies>
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
编写Properties文件
在CLASSPATH下建立log4j.properties
log4j.properties
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file
#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/kuang.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][qinjiang%d{yy-MM-dd}][%c]%m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
配置log4j为日志的实现
mybatis-config.xml
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
log4j的使用
直接测试运行刚才的查询
简单使用:UserDaoTest.java
在要使用log4j的类中导入包
import org.apache.log4j.Logger;
日志对象,参数为当前的class
static Logger logger = Logger.getLogger(UserDaoTest.class);
日志级别
@Test
public void getUserLike(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
logger.info("测试,进入getUserLike方法成功!");
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(1);
System.out.println(user);
sqlSession.close();
}
@Test
public void testLog4j(){
logger.info("info:进入了testLog4j");
logger.debug("debug:进入了testLog4j");
logger.error("error:进入了testLog4j");
}
6. 分页
减少处理量
6.1 Limit实现分页
6.1.1 语法
SELECT * from user limit startIndex,pageSize;
6.1.2 使用Mybatis实现分页,核心SQL
接口
//分页
List<User> getUserByLimit(Map<String,Integer> map);
UserMapper.xml
<!--分页-->
<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
select * from user limit #{startIndex},#{pageSize}
</select>
测试:UserDaoTest.java
@Test
public void getUserByLimit(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Integer> map = new HashMap<String, Integer>();
map.put("startIndex",1);
map.put("pageSize",2);
List<User> userList = mapper.getUserByLimit(map);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
6.2 RowBounds分页
不再使用SQL进行分页
接口
//分页2
List<User> getUserByRowBounds();
导入
<!--分页2-->
<select id="getUserByRowBounds" resultMap="UserMap">
select * from user
</select>
测试
@Test
public void getUserByRowBounds(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
//RowBounds实现
RowBounds rowBounds = new RowBounds(1, 2);
//通过Java代码层面实现分页
List<User> userList = sqlSession.selectList("com.kuang.dao.UserMapper.getUserByRowBounds",null,rowBounds);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
6.3 Mybatis分页插件PageHelper
7. 使用注解开发
7.1 面向接口编程
定义与实现的分离
7.2 使用注解开发
注解在接口上实现
@Selext("select * from user")
List<User> getUsers();
需要核心配置文件中绑定接口
<!--绑定接口-->
<mappers>
<mapper class="com.kuang.dao.UserMapper"/>
</mappers>
测试
本质:反射机制
底层:动态代理
public class UserMapperTest {
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
//底层主要应用反射
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.deleteUser(5);
sqlSession.close();
}
}
7.3 Mybatis执行流程
7.4 注解增删改查CRUD
关于@Param()的注解
-
基本类型的参数或者String类型,需要加上
-
引用类型不需要加
-
如果只有一个基本类型的话,可以忽略
-
在SQL中引用的就是@Param()中设定的属性名
在工具类创建的时候实现自动提交事务
public interface UserMapper {
@Select("select * from user")
List<User> getUsers();
// 方法存在多个参数,所有的参数前面必须加上 @Param("id")注解
@Select("select * from user where id = #{id}")
User getUserByID(@Param("id") int id);
@Insert("insert into user(id,name,pwd) values (#{id},#{name},#{password})")
int addUser(User user);
@Update("update user set name=#{name},pwd=#{password} where id = #{id}")
int updateUser(User user);
@Delete("delete from user where id = #{uid}")
int deleteUser(@Param("uid") int id);
}
测试
User userByID = mapper.getUserByID(1);
System.out.println(userByID);
mapper.addUser(new User(5,"Hello","123123"));
mapper.updateUser(new User(5,"to","21312"));
7.5 Lombok
通过注解的形式自动生成构造器
-
java library
-
plugs
-
build tools
-
with one annocation your class
7.5.1 使用步骤
在IDEA中安装Lombok插件
在项目中导入lambok的jar包
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
1|@Getter and @Setter 2|@FieldNameConstants 3|@ToString 4|@Data 5|@EqualsAndHashCode 6|@SneakyThrows 7|@Wither 8|@Accessors 9|@Value 10|@Delegate 11|@Singular 12|@Builder 13|@Log,@Log4j,@Log4j2,@slf4j,@xslf4j,@CommonsLog,@JBossLog,@Flogger 14|@AllArgsConstructor,@RequireArgsConstructor
在实体类上加上注解
package com.kuang.pojo;
import lombok.*;
@Data //无参构造,get set hashcode tostring
@AllArgsConstructor //
@NoArgsConstructor
public class User {
private int id;
private String name;
private String password;
}
8. 复杂查询环境搭建
8.1多对一的处理
多个学生对应一个老师
8.1.1 创建数据库
新的表teacher和student
student中的tid:指向teacher这个表当中id为tid的老师
CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO teacher (`id`, `name`) VALUES (1, 'teacher');
CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),`student`
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO student (`id`, `name`, `tid`) VALUES (1, '学生1',1);
INSERT INTO student (`id`, `name`, `tid`) VALUES (2, '学生2',1);
INSERT INTO student (`id`, `name`, `tid`) VALUES (3, '学生3',1);
INSERT INTO student (`id`, `name`, `tid`) VALUES (4, '学生4',1);
INSERT INTO student (`id`, `name`, `tid`) VALUES (5, '学生5',1);
8.1.2 搭建测试环境
-
IDEA安装Lombok插件
-
引入Maven依赖
<dependencies> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> </dependency> </dependencies>
-
在代码中增加注解
<dependencies> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> </dependency> </dependencies>
package com.kuang.pojo; import lombok.Data; @Data public class Student { private int id; private String name; //学生需要关联一个老师! private Teacher teacher; }
-
编写实体类对应的Mapper接口
package com.kuang.dao; import com.kuang.pojo.Student; import java.util.List; public interface StudentMapper { }
package com.kuang.dao; import com.kuang.pojo.Teacher; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; public interface TeacherMapper { }
-
编写Mapper接口对应的mapper.xml配置文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.kuang.dao.StudentMapper"> </mapper>
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.kuang.dao.TeacherMapper"> </mapper>
8.1.3 按查询嵌套处理
SQL中的子查询
-
给StudentMapper接口增加方法
//查询所有的学生信息,以及对应的老师的信息! public List<Student> getStudent();
-
编写对应Mapper文件
<?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.kuang.mapper.StudentMapper"> <!-- 需求:获取所有学生及对应老师的信息 思路: 1. 获取所有学生的信息 2. 根据获取的学生信息的老师ID->获取该老师的信息 3. 思考问题,这样学生的结果集中应该包含老师,该如何处理呢,数据库中我们一般使用关联查询? 1. 做一个结果集映射:StudentTeacher 2. StudentTeacher结果集的类型为 Student 3. 学生中老师的属性为teacher,对应数据库中为tid。 多个 [1,...)学生关联一个老师=> 一对一,一对多 4. 查看官网找到:association – 一个复杂类型的关联;使用它来处理关联查询 --> <select id="getStudents" resultMap="StudentTeacher"> select * from student </select> <resultMap id="StudentTeacher" type="Student"> <!--association关联属性 property属性名 javaType属性类型 column在多的一方的表中的列名--> <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/> </resultMap> <!-- 这里传递过来的id,只有一个属性的时候,下面可以写任何值 association中column多参数配置: column="{key=value,key=value}" 其实就是键值对的形式,key是传给下个sql的取值名称,value是片段一中sql查询的字段名。 --> <select id="getTeacher" resultType="teacher"> select * from teacher where id = #{id} </select> </mapper>
-
在Mybatis配置文件中注册Mapper
<resultMap id="StudentTeacher" type="Student"> <!--association关联属性 property属性名 javaType属性类型 column在多的一方的表中的列名--> <association property="teacher" column="{id=tid,name=tid}" javaType="Teacher" select="getTeacher"/> </resultMap> <!-- 这里传递过来的id,只有一个属性的时候,下面可以写任何值 association中column多参数配置: column="{key=value,key=value}" 其实就是键值对的形式,key是传给下个sql的取值名称,value是片段一中sql查询的字段名。 --> <select id="getTeacher" resultType="teacher"> select * from teacher where id = #{id} and name = #{name} </select>
-
测试
@Test public void testGetStudents(){ SqlSession session = MybatisUtils.getSession(); StudentMapper mapper = session.getMapper(StudentMapper.class); List<Student> students = mapper.getStudents(); //通过遍历学生列表,分别输出每个学生的姓名和对应的老师姓名 for (Student student : students){ System.out.println( "学生名:"+ student.getName() +"\t老师:"+student.getTeacher().getName()); } }
8.1.4 按结果嵌套处理
SQL中的联表查询
-
接口方法编写
public List<Student> getStudents2();
-
编写对应的Mapper文件
<!-- 按查询结果嵌套处理 思路: 1. 直接查询出结果,进行结果集的映射 --> <select id="getStudents2" resultMap="StudentTeacher2" > select s.id sid, s.name sname , t.name tname from student s,teacher t where s.tid = t.id </select> <resultMap id="StudentTeacher2" type="Student"> <id property="id" column="sid"/> <result property="name" column="sname"/> <!--关联对象property 关联对象在Student实体类中的属性--> <association property="teacher" javaType="Teacher"> <result property="name" column="tname"/> </association> </resultMap>
-
在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核心配置文件--> <configuration> <!--引入外部配置文件--> <properties resource="db.properties"/> <settings> <!--标准的日志工厂实现--> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings> <!--可以给实体类起别名--> <typeAliases> <package name="com.kuang.pojo"/> </typeAliases> <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="${pwd}"/> </dataSource> </environment> </environments> <mappers> <mapper class="com.kuang.dao.TeacherMapper"/> <mapper class="com.kuang.dao.StudentMapper"/> </mappers> </configuration>
-
测试
@Test public void testGetStudents2(){ SqlSession session = MybatisUtils.getSession(); StudentMapper mapper = session.getMapper(StudentMapper.class); List<Student> students = mapper.getStudents2(); for (Student student : students){ System.out.println( "学生名:"+ student.getName() +"\t老师:"+student.getTeacher().getName()); } }
8.2 一对多的处理
一个老师对应多个学生
关联 association:用于一对一和多对一
集合 collection:一对多
javaType和ofType指定对象类型
JavaType指定pojo中的属性类型
ofType指定映射到list集合属性中pojo的类型
8.2.1 实体类的编写
@Data
public class Student{
private int id;
private String name;
private int id;
}
@Data
public class Teacher{
private int id;
private String name;
//一个老师多个学生
private List<Student> student;
}
8.2.2 搭建测试环境
导入lombok->接口->接口对应的xml文件
package com.kuang.dao;
import com.kuang.pojo.Student;
import java.util.List;
public interface StudentMapper {
}
package com.kuang.dao;
import com.kuang.pojo.Teacher;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface TeacherMapper {
}
8.2.3 按结果嵌套处理
-
TeacherMapper接口编写方法
//获取指定老师以及老师下所有学生 public Teacher getTeacher(int id);
-
编写接口对应的Mapper配置文件
<mapper namespace="com.kuang.mapper.TeacherMapper"> <!-- 思路: 1. 从学生表和老师表中查出学生id,学生姓名,老师姓名 2. 对查询出来的操作做结果集映射 1. 集合的话,使用collection! JavaType和ofType都是用来指定对象类型的 JavaType是用来指定pojo中属性的类型 ofType指定的是映射到list集合属性中pojo的类型。 --> <select id="getTeacher" resultMap="TeacherStudent"> select s.id sid, s.name sname , t.name tname, t.id tid from student s,teacher t where s.tid = t.id and t.id=#{id} </select> <resultMap id="TeacherStudent" type="Teacher"> <result property="name" column="tname"/> <!-- •ofType•属性指定了集合元素类型为 •Student•--> <collection property="students" ofType="Student"> <result property="id" column="sid" /> <result property="name" column="sname" /> <result property="tid" column="tid" /> </collection> </resultMap> </mapper>
-
将Mapper文件注册到Mybatis-config.xml文件中
<mappers> <mapper resource="mapper/TeacherMapper.xml"/> </mappers>
-
测试
@Test public void testGetTeacher(){ SqlSession session = MybatisUtils.getSession(); TeacherMapper mapper = session.getMapper(TeacherMapper.class); Teacher teacher = mapper.getTeacher(1); System.out.println(teacher.getName()); System.out.println(teacher.getStudents()); }
8.2.4 按查询嵌套处理
-
TeacherMapper接口编写方法
public Teacher getTeacher2(int id);
-
编写接口对应的Mapper配置文件
<select id="getTeacher2" resultMap="TeacherStudent2"> select * from teacher where id = #{id} </select> <resultMap id="TeacherStudent2" type="Teacher"> <!--column是一对多的外键 , 写的是一的主键的列名--> <collection property="students" javaType="ArrayList" ofType="Student" column="id" select="getStudentByTeacherId"/> </resultMap> <select id="getStudentByTeacherId" resultType="Student"> select * from student where tid = #{id} </select>
-
将Mapper文件注册到Mybatis-config文件中
-
测试
@Test public void testGetTeacher2(){ SqlSession sqlSession = MybatisUtils.getSession(); TeacherMapper mapper = session.getMapper(TeacherMapper.class); Teacher teacher = mapper.getTeacher2(1); System.out.println(teacher.getName()); System.out.println(teacher.getStudents()); }
9. 动态SQL
9.1 简介
动态sql指的是根据不同的查询条件生成不同的sql语句
使用 mybatis 动态SQL,通过 if, choose, when, otherwise, trim, where, set, foreach等标签,可组合成非常灵活的SQL语句,从而在提高 SQL 语句的准确性的同时,也大大提高了开发人员的效率。
9.2 环境搭建
9.2.1 新建数据库表 blog
字段: id, title, author, creat_time, views
CREATE TABLE `blog` (
`id` VARCHAR(50) NOT NULL COMMENT '博客id',
`title` VARCHAR(100) NOT NULL COMMENT '博客标题',
`author` VARCHAR(30) NOT NULL COMMENT '博客作者',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`views` INT(30) NOT NULL COMMENT '浏览量'
) ENGINE=INNODB DEFAULT CHARSET=utf8
9.2.2 创建Mybatis基础工程
package com.kuang.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
//sqlSessionFactory --> sqlSession
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static{
try {
//使用Mybatis第一步:获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}
}
9.2.3 IDutil工具类
public class IDUtil{
public static String genld(){
return UUID.randomUUID().toSting().replaceAll("-","");
}
}
9.2.4 实体类编写
public class IDUtil{
public static String genld(){
return UUID.randomUUID().toSting().replaceAll("-","");
}
}
9.2.5 编写Mapper接口以及xml文件
public interface BlogMapper{
}
<?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.kuang.mapper.BlogMapper">
</mapper>
9.2.6 mybatis核心配置文件,下划线驼峰自动转换
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--注册Mapper.xml-->
<mappers>
<mapper resource="mapper/BlogMapper.xml"/>
</mappers>
9.2.7 插入初始数据
-
编写接口
//新增一个博客 int addBlog(Blog blog);
-
sql配置文件
<insert id="addBlog" parameterType="blog"> insert into blog (id, title, author, create_time, views) values (#{id},#{title},#{author},#{createTime},#{views}); </insert>
-
初始化博客方法
import com.kuang.dao.BlogMapper; import com.kuang.pojo.Blog; import com.kuang.utils.IDutils; import com.kuang.utils.MybatisUtils; import org.apache.ibatis.cache.Cache; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; public class MyTest { @Test public void addInitBlog(){ SqlSession session = MybatisUtils.getSqlSession(); BlogMapper mapper = session.getMapper(BlogMapper.class); Blog blog = new Blog(); blog.setId(IDutils.getId()); blog.setTitle("Mybatis"); blog.setAuthor("Leo"); blog.setCreateTime(new Date()); blog.setViews(9999); //使用 •mapper.addBlog(blog)•方法将该Blog对象插入数据库中。 mapper.addBlog(blog); //通过设置不同的title属性值,重复调用 •mapper.addBlog(blog)•方法向数据库中添加多个博客信息。 blog.setId(IDutils.getId()); blog.setTitle("Java"); mapper.addBlog(blog); blog.setId(IDutils.getId()); blog.setTitle("Spring"); mapper.addBlog(blog); blog.setId(IDutils.getId()); blog.setTitle("微服务"); mapper.addBlog(blog); session.close(); } @Test public void queryBlogIF(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); HashMap map = new HashMap(); map.put("author","Leo"); List<Blog> blogs = mapper.queryBlogIF(map); for (Blog blog : blogs) { System.out.println(blog); } sqlSession.close(); } @Test public void queryBlogForEach(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); HashMap map = new HashMap(); ArrayList<Integer> ids = new ArrayList<Integer>(); ids.add(1); ids.add(2); ids.add(3); map.put("ids",ids); List<Blog> blogs = mapper.queryBlogForeach(map); for (Blog blog : blogs) { System.out.println(blog); } sqlSession.close(); } }
9.3 if语句
根据作者的名字和博客名字来查询博客,如果作者名字为空,只根据博客名字查询。反之用作者名查询
-
编写接口类
//需求1 List<Blog> queryBlogIf(Map map);
-
编写SQL语句
<!--需求1: 根据作者名字和博客名字来查询博客! 如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询 select * from blog where title = #{title} and author = #{author} --> <select id="queryBlogIf" parameterType="map" resultType="blog"> select * from blog where <if test="title != null"> title = #{title} </if> <if test="author != null"> and author = #{author} </if> </select>
-
测试
-
如果 author 等于 null,那么查询语句为 select * from user where title=#{title}
-
如果title为空,查询语句为 select * from user where and author=#{author},是错误的 SQL 语句[查看后面的where语句]
@Test public void testQueryBlogIf(){ SqlSession session = MybatisUtils.getSession(); BlogMapper mapper = session.getMapper(BlogMapper.class); HashMap<String, String> map = new HashMap<String, String>(); map.put("title","Mybatis"); map.put("author","Leo"); List<Blog> blogs = mapper.queryBlogIf(map); System.out.println(blogs); session.close(); }
-
9.4 常用标签
9.4.1 where语句
修改上面的SQL语句
“where”标签:如果它包含的标签中有返回值,插入一个‘where’。
如果标签返回的内容以AND 或OR 开头,会剔除。
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from blog
<!--<if>•标签配合 •<where>•标签实现动态条件查询-->
<!--在 •<select>•标签内部使用 •<where>•标签来定义查询条件。•<where>•标签会自动处理条件中的逻辑关系(如AND、OR等),对于第一个条件,如果条件成立会添加WHERE关键字,之后的条件会根据情况自动添加AND或者OR关键字。-->
<where>
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>
9.4.2 Set语句
-
编写接口方法
int updateBlog(Map map);
-
sql配置文件
<update id="updateBlog" parameterType="map"> update blog <set> <if test="title != null"> title = #{title}, </if> <if test="author != null"> author = #{author} </if> </set> where id = #{id}; </update>
-
测试
@Test public void testUpdateBlog(){ SqlSession session = MybatisUtils.getSession(); BlogMapper mapper = session.getMapper(BlogMapper.class); HashMap<String, String> map = new HashMap<String, String>(); map.put("title","动态SQL"); map.put("author","Leo"); map.put("id","0ae808b526e245c892996c5e3528afb6"); mapper.updateBlog(map); session.close(); }
9.4.3 choose语句
有时候用不到所有的查询条件,只选择其中一个,即满足一个查询条件即可。choose类似于Java里的switch语句
-
编写接口方法
List<Blog> queryBlogChoose(Map map);
-
sql配置文件
<select id="quertBlogChoose" parameterType="map" resultType="blog"> select * from blog <where> <choose> <when test="title != null"> title = #{title} </when> <when test="author != null"> and author = #{author} </when> <otherwise> and views = #{views} </otherwise> </choose> </where> </select>
-
测试类
@Test public void testQueryBlogChoose(){ SqlSession session = MybatisUtils,getSession(); BlogMapper mapper = session.getMapper(BlogMapper,class); HashMap<String, Object> map = new HashMap<String, Object>(); map.put("title","Java"); map.put("author","Leo"); map.put("views",9999); List<Blog> blogs = mapper.queryBlogChoose(map); System.out.println(blogs); session,close(); }
9.4.4 SQL片段
增加代码重用性,简化代码,抽取代码直接调用
注意:
最好基于单表定义sql片段,提高片段可重用性
在sql片段中不要包括where
-
提取SQL片段
<sql id="if-title-author"> <if test="title != null"> title = #{title} </if> <if test="author != null"> and author = #{author} </if> </sql>
-
引用SQL片段
<select id="queryBlogIf" parameterType="map" resultType="blog"> select * from blog <where> <!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace --> <include refid="if-title-author"></include> <!-- 在这里还可以引用其他的 sql 片段 --> </where> </select>
9.5 Foreach
修改数据库id为1,2,3
要求:查询blog表中id分别为1,2,3的博客信息
-
编写接口
List<Blog> queryBlogForeach(Map map);
-
编写SQL语句
<select id="queryBlogForeach" parameterType="map" resultType="blog"> select * from blog <where> <!-- collection:指定输入对象中的集合属性 item:每次遍历生成的对象 open:开始遍历时的拼接字符串 close:结束时拼接的字符串 separator:遍历对象之间需要拼接的字符串 select * from blog where 1=1 and (id=1 or id=2 or id=3) --> <!-- collection:指定输入对象中的集合属性 item:每次遍历生成的对象 open:开始遍历时的拼接字符串 close:结束时拼接的字符串 separator:遍历对象之间需要拼接的字符串 select * from blog where 1=1 and (id=1 or id=2 or id=3) --> <foreach collection="ids" item="id" open="and("close=")" separator="or"> id = #{id} </foreach> </where> </select>
-
测试
@Test public void testQueryBlogForeach(){ SqlSession session = MybatisUtils.getSession(); BlogMapper mapper = session.getMapper(BlogMapper.class); //创建一个HashMap对象 •map•,并在 •map•中添加一个键值对,键为"ids",值为一个包含ID列表的ArrayList HashMap map = new HashMap(); List<Integer> ids = new ArrayList<Integer>(); ids.add(1); ids.add(2); ids.add(3); map.put("ids",ids); List<Blog> blogs = mapper,quesyBlogForeach(map); System.out.println(blogs); session.close(); }
10. 缓存
10.1 简介
什么是缓存
-
存在内存中的临时数据
-
将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题
为什么使用缓存
-
减少和数据库的交互次数,减少系统开销,提高系统效率
什么样的数据可以使用缓存
-
经常查询并且不经常改变的数据
10.2 Mybatis缓存
-
查询缓存特性:定制和配置缓存
-
默认定义两级缓存
-
默认情况只有一级缓存开启(SqlSession级别的缓存,也称为本地缓存)
-
二级缓存需要手动开启和配置,基于namespace级别的缓存,也称为本地缓存)
-
提高扩展性定义缓存接口Cache,通过实现Cache接口自定义二级缓存
-
10.3 一级缓存
本地缓存:
与数据库同一次会话期间查询到的数据放在本地缓存中
如果需要获取相同的数据,直接从缓存中拿,不用再去查询数据库
一级缓存就是一个map
10.3.1 测试
10.4.3 Mybatis缓存原理
sqlSession相同,手动清除一级缓存
@Test public void testQueryUserById(){ SqlSession session = MybatisUtils.getSession(); UserMapper mapper = session.getMapper(UserMapper.class); User user = mapper.queryUserById(1); System.out.println(user); session.clearCache();//手动清除缓存 User user2 = mapper.queryUserById(1); System.out.println(user2); System.out.println(user==user2); session.close(); }
10.4 二级缓存
全局缓存:基于namespace级别的缓存,一个名称空间对应一个二级缓存
工作机制
一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
当前会话关闭,这个会话对应的一级缓存消失;需求是会话关闭就把一级缓存的数据保存在二级缓存中
新的会话查询信息从二级缓存中获取内容
不同mapper查出的数据会放在自己对应的缓存(map)中
10.4.1 使用步骤
开启全局缓存:mybatis-config.xml
<setting name="cacheEnabled" value="true"/>
去每个mapper.xml中配置使用二级缓存:xxxMapper.xml
<!--在当前Mapper.xml中使用二级缓存--> <cache type="org.mybatis.caches.ehcache.EhcacheCache"/> <select id="queryUserById" resultType="user" useCache="false"> select * from user where id = #{id} </select>
下面这个更高级的配置创建了一个FIFO缓存,每隔60秒刷新,最多可以存储结果对象或列表的512个引用,返回的对象被认为是只读。对它们进行修改可能会在不同线程中的调用者产生冲突。
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
代码测试
所有的实体类先实现序列化接口
测试代码如下:
@Test public void testQueryUserById(){ SqlSession session = MybatisUtils.getSession(); SqlSession session2 = MybatisUtils.getSession(); UserMapper mapper = session.getMapper(UserMapper.class); UserMapper mapper2 = session2.getMapper(UserMapper.class); User user = mapper.queryUserById(1); System.out.println(user); session.close(); User user2 = mapper2.queryUserById(1); System.out.println(user2); System.out.println(user==user2); session2.close(); }
10.4.2 结论
-
在mybatis中加入日志,方便测试结果
-
编写接口方法
//根据id查询用户 User queryUserById(@Param("id") int id);
-
接口对应的Mapper文件
<select id="queryUserById" resultType="user"> select * from user where id = #{id} </select>
-
测试
@Test public void testQueryUserById(){ SqlSession session = MybatiesUtils.getSession(); UserMapper mapper = session.getMapper(UserMapper.class); //调用 •mapper.queryUserById(1)•方法,参数传入用户ID 1,来查询数据库中ID为1的用户信息。将查询结果存储在 •user•对象中 User user = mapper.queryUserById(1); System.out.println(user); User user2 = mapper.queryUserById(1); System.out.println(user==user2); session.close(); }
10.3.2 一级缓存失效的四种情况
-
一级缓存是SqlSession级别的缓存,是一直开启的
-
一级缓存失效的情况:没有使用到当前的一级缓存,还需要再向数据库中发起一次查询请求
sqlSession不同
发送了两条SQL语句
结论:每个sqlSession中的缓存相互独立
@Test public void testQueryUserById(){ SqlSession session = MybatisUtils.getSession(); SqlSession session2 = MybatisUtils.getSession(); UserMapper mapper = session.getMapper(UserMapper.class); UserMapper mapper2 = session2.getMapper(UserMapper.class); User user = mapper.queryUserById(1); System.out.println(user); User user2 = mapper2.queryUserById(1); System.out.println(user2); System.out.println(user==user2); session.close(); session2.close(); }
sqlSession相同,查询条件不同
发送了两条SQL查询语句
结论:当前缓存中不存在这个数据
@Test public void testQueryUserById(){ SqlSession session = MybatisUtils.getSession(); UserMapper mapper = session.getMapper(UserMapper.class); UserMapper mapper2 = session.getMapper(UserMapper.class); User user = mapper.queryUserById(1); System.out.println(user); User user2 = mapper2.queryUserById(2); System.out.println(user2); System.out.println(user==user2); session.close(); }
sqlSessino相同。两次查询之间执行了增删改操作
查询中间执行了增删改操作之后重新执行了
-
-
增加方法
//修改用户 int updateUser(Map map);
-
编写SQL
<!--更新用户信息的示例--> <update id="updateUser" parameterType="map"> <!--在 •<update>•标签中,使用 •id•属性指定了操作的ID,•parameterType•属性指定了参数类型为 •map•。你可以根据实际情况调整参数类型为具体的Java对象。 在 •<update>•标签内部,编写了具体的更新语句。这个示例中的更新语句是将 •user•表中 •name•字段的值更新为传入的参数 •name•的值,条件是 •id•字段等于传入的参数 •id•的值。--> update user set name = #{name} where id = #{id} </update>
-
测试
@Test public void testQueryUserById(){ SqlSession session = MybatisUtils.getSession(); UserMapper mapper = session.getMapper(UserMapper.class); User user = mapper.queryUserById(1); System.out.println(user); HashMap map = new HashMap(); map.put("name","kuangshen"); map.put("id",4); mapper.updateUser(map); User user2 = mapper.queryUserById(1); System.out.println(user2); System.out.println(user==user2); session.close(); }
-
只要开了二级缓存,在同一个Mapper中查询可以在二级缓存中拿到数据
-
查出的数据都会被默认先放在一级缓存中
-
只有会话提交或者关闭,一级缓存的数据才会转入二级缓存
10.4.3 Mybatis缓存原理
10.4.4 自定义缓存Ehche
第三方缓存实现:EhCache,是一种广泛使用的java分布式缓存,用于通用缓存
-
要在应用程序中使用Ehcache,需要引入依赖的Jar包
pom.xml
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache --> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.1.0</version> </dependency>
-
在mapper.xml中使用对应的缓存
<mapper namespace="org.acme.FooMapper"> <cache type = "org.mybatis.caches.ehcache.EhcacheCache"/> </mapper>
-
编写ehcache.xml文件
如果在加载时未找到/ehcache.xml资源或出现问题,则将使用默认配置
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<!--
diskStore:缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释:
user.home – 用户主目录
user.dir – 用户当前工作目录
java.io.tmpdir – 默认临时文件路径
-->
<diskStore path="./tmpdir/Tmp_EhCache"/>
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>
<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>
<!--
defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
-->
<!--
name:缓存名称。
maxElementsInMemory:缓存最大数目
maxElementsOnDisk:硬盘最大缓存个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
overflowToDisk:是否保存到磁盘,当系统当机时
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
FIFO,first in first out,这个是大家最熟的,先进先出。
LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
-->
</ehcache>