Mybatis
简介:
什么mybatis
mybatis是一款优秀的持久层框架
支持自定义sql,储存过程以及高级映射
MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作
MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
2013年11月迁移到GitHub。
获取mybatis:
Maven仓库:
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
Github:https://github.com/mybatis/mybatis-3/releases
中文文档:https://mybatis.org/mybatis-3/zh/index.html
持久化:
数据持久化:持久化就是将程序的数据在持久状态和临时状态转化的过程
临时状态: 放在内存中的数据,断电就会消失
持久状态: 放在数据库(JDBC) 通过io来保证文件持久化
为什么要文件持久化?
有一些对象不能让他丢失
并且内存太贵了,需要转换到硬盘来进行持久化存储
持久层
比如Dao层,Service层,Controller层
- 都是为了完成持久化工作的代码块,
- 并且层界限都十分明显
mybaits的优点:
帮助程序员将数据存在数据库中,操作方便,
传统的jdbc代码太复杂了,为了简化,框架,和自动化
当然,不用Mybatis也可以,只不过更容易上手,技术没有高低之分,使用技术的人才有
具体一点的优点有:
- 简单易学
- 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响,sql写在xml中,便于统一管理和优化,通过sql语句合一满足操作数据库的所有需求
- 解耦:
解除了sql和程序代码的耦合程度,通过提供DAO层,将业务逻辑和数据访问逻辑分离了,使系统的设计更急的清晰,更容易维护,更容易进行单元测试,失去了和代码的分离也提高了代码的可维护性 - 提供映射标签,支持对象与数据库的orm字段的关系映射
- 提供对象关系映射标签,支持对象关系组件维护
- 提供xml标签,支持编写动态sql
当然,最重要的是使用的人多
mybatis核心知识点:
mybatis三个重要的接口,(笔试会问)
SqlSessionFactoryBuilder
这个类可以被实例化,使用和丢弃,一旦创建了sqlsessionfactorybuilder,就不再需要它了,因此sqlsessionfactorybuilder实例化的最佳作用于就是方法作用于中,(也就是局部变量,或者直接写在工具类当中),这个类可以被重用并创建多个sqlsessionfactory实例,但最好还是不要一直保留,以保证所有的xml解析资源可以被释放给更重要的事情
SqlSessionFactory
sqlsessionfactory一旦被创建就应该再应用的运行期间一直存在,没有任何理由丢弃它或者重新创建一个新的实例,使用sqlsessionfactory最好在应用运行期间不重新创建,多次重新创建sqlsessionfactory被视为一中"坏习惯",因此sqlsessionfactory的最佳作用域就是应用作用域,有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式
sqlsession
每一个线程都应该有自己的sqlsession实例,sqlsession的实例线程不安全,因此是不能被共享的,所以它的最佳作用域就是请求或者方法作用域中,绝对不能将sqlsession的实例放在一个类的静态域,甚至一个类的实例变量也不行,也绝对不能将sqlsession实例的引用放在任何类型的托管作用域中,比如servlet框架中的HTTPSession,如果正在使用一种web框架,考虑将sqlsession放在一格和HTTP请求相似的作用域中,换句话说,每次收到HTTP请求就可以打开一个sqlsession,返回一个响应之后就关闭它,这个关闭操作很重要,为了确保每次都能执行关闭操作,应该把这个关闭操作放在finally块
第一个Mybatis程序
思路:
搭建环境–>导入Mybatis–>编写代码–>测试
搭建环境(建表)
CREATE DATABASE mybatis;
USE ‘mybatis’;
CREATE TABLE USER(
id INT PRIMARY KEY,
NAME VARCHAR(20) DEFAULT NULL, #代表默认为空
pwd VARCHAR(20) DEFAULT NULL #代表默认为空
)ENGINE=INNODB DEFAULT CHARSET=utf8;
#ENGINE=INNODB代表设置引擎
新建项目:
新建一个普通的maven项目
删除src目录
导入maven依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 父工程-->
<groupId>com.xg</groupId>
<artifactId>Mybatis-study</artifactId>
<version>1.0-SNAPSHOT</version>
<!--导入依赖-->
<dependencies>
<!-- mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- mybatis驱动-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<!-- junit测试单元-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
</dependencies>
</project>
创建一个模块
编写一格mybatis核心配置文件(在resources中新建一格file文件) 官网上就有,只需要将值修改一下就好了
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--核心配置文件-->
<configuration>
<environments default="development">
<!-- 此处可以使用多套环境,上面那行default="development"代表默认使用那一套环境-->
<environment id="development">
<!-- 下面的transactionManager type="JDBC"代表默认使用jdbc的事务管理器-->
<transactionManager type="JDBC"/>
<!--dataSource type="POOLED"代表默认连接是连接池的实现-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<!-- 普通的&符号在xml中无法使用,需要用&进行转义才行-->
<property name="url" value="jdbc:mysql://localhost:3306/?serverTimezone=GMT/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="981216"/>
</dataSource>
</environment>
</environments>
</configuration>
编写mybatis工具类:
官方中文注释的三行代码就是为了获取工厂,然后再从工厂中生产sqlsession
//sqlSessionFactory
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
private static InputStream inputStream;
static{
try {
//使用mybatis第一步:获取sqlSessionFactory对象 (把资源加载进工厂)
//以下是在mybatis官方中文文档中直接获取的三段固定代码
String resource = "mybatis-config.xml"; //从自定义的xml配置文件获取资源文件名
inputStream= Resources.getResourceAsStream(resource); //获取资源文件流 (注意build中会关闭流)
sqlSessionFactory= new SqlSessionFactoryBuilder().build(inputStream);//最后将流加载进工厂
} catch (IOException e) {
e.printStackTrace();
}
}
//既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
// SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。 (然后创建一个可以执行sql的对象)
public static SqlSession getSqlSession(){
//此处的sqlSession其实就是用来操作数据库的
return sqlSessionFactory.openSession();
}
}
mybatis核心配置文件:(mybatis-config.xml):
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--核心配置文件-->
<configuration>
<!-- 引入外部配置文件-->
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="981216"/>
</properties>
<typeAliases>
<!-- xml设置别名的方法还有 <typeAlias type="com.xg.pojo.User" alias="User"></typeAlias>-->
<package name="com.xg.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="${password}"/>
</dataSource>
</environment>
<environment id="test">
<!-- 下面的transactionManager type="JDBC"代表默认管理器除了jdbc还有一个不过那个过时了-->
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="981216"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- <mapper resource="com/xg/dao/UserMapper.xml"/>-->
<!-- <mapper class="com.xg.dao.UserMapper"></mapper>-->
<package name="com.xg.dao"/>
</mappers>
</configuration>
编写代码:
POJO:略
mapper接口:
public interface UserDao {
List<User> getUserList();
}
接口实现类 由原来的UserDaoImpl转变为一个Mapper(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">
<!--namespace:命名空间,用来绑定一个对应的Dao/Mapper接口-->
<!--此处直接指向定义好的Dao/Mapper接口路径才能产生交集-->
<mapper namespace="com.xg.dao.UserDao">
<!-- 此处的id并不是数据库表中的id,而是实现接口后具体重写的方法名-->
<!-- resultType="com.xg.pojo.User" 就代表返回的类型-->
<select id="getUserList" resultType="com.xg.pojo.User">
select * from mybatis.user
</select>
</mapper>
测试:
MappersRegistry是什么?
核心配置文件中注册mapper
报错实例:
文件未注册:
org.apache.ibatis.binding.BindingException: Type interface com.xg.dao.UserMapper is not known to the MapperRegistry.
解决方法:在resource资源配置文件中添加以下配置进行注册
每一个mapper.xml配置文件都必须在mabatis核心配置文件中注册
<!-- 每一个Mapper.XML都需要在Mybatis核心配置文件中注册-->
<mappers>
<mapper resource="com/xg/dao/UserMapper.xml"/>
</mappers>
初始化异常/失败:
java.lang.ExceptionInInitializerError
解决办法:
在pom.xml中配置以下代码(直接复制粘贴,不要管里面的星号)
<!--在build中配置resources,来防止我们资源导出失败的问题-->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
3 字节的 UTF-8 序列的字节 3 无效解决方式:
删除mybatis-config.xml中的中文注释
在pom.xml中配置以下代码
<properties>
<!-- 设置默认编码 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
junit测试:
PS:测试时建议每一个测试都对应一个实体类
测试代码如下:
public class UserMapperTest {
@Test
public void test(){
//第一步;获得sqlsession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
//第二步:执行sql
// 执行方式一:getMapper
// 我们需要把UserMapper的对象获取出来才能获取它的方法(getMapper)
//而getMapper就是通过接口的class对象来get的
/*sqlSession要想执行就必须要先getMapper获得sql,而sql在UserMapper的实现类UserMapper.xml中被实现了
所以我们只需要get接口就可以了,get接口之后就会返回接口类型,然后接口类型再执行不同的方法
* */
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();
for (User user:userList) {
System.out.println(user);
}
//关闭sqlsession
sqlSession.close();
}
}
CRUD
这里以查询为例:
select 查询
选择查询语句;
id: 对应namespace中的方法名
resultType: sql语句执行的返回值
parameterType : 执行结果返回的参数类型
namespace
接口实现的xml文件中namespace中的包名要和Dao/Mapper接口的包名一致,
直接指向定义好的Dao/Mapper接口路径才能产生交集
如下为接口实现的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接口-->
<!--此处直接指向定义好的Dao/Mapper接口路径才能产生交集-->
<mapper namespace="com.xg.dao.UserMapper">
<!-- 此处的id并不是数据库表中的id,而是实现接口后具体重写的方法-->
<!-- resultType="com.xg.pojo.User" 就代表返回的类型-->
<select id="getUserList" resultType="com.xg.pojo.User">
select * from mybatis.user
</select>
</mapper>
同时有以下三种方法将mapper.xml注册到mybatis-config.xml中
<mappers>
<!-- <mapper resource="com/xg/dao/UserMapper.xml"/>-->
<!-- <mapper class="com.xg.dao.UserMapper"></mapper>-->
<package name="com.xg.dao"/>
</mappers>
详细步骤
然后就是编写mapper接口:
public interface UserMapper { //接口中每一个方法都要表明返回的类型
//查询全部用户
List<User> getUserList();
//根据id查询用户
User getUserById(int id);
//insert 添加一个用户
int addUser(User user);
//修改用户
int updateUser(User user);
//删除用户
int deleteUser(int id);
}
编写对应mapper的sql语句
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xg.dao.UserMapper">
<select id="getUserList" resultType="com.xg.pojo.User">
select * from mybatis.user
</select>
<select id="getUserById" parameterType="int" resultType="com.xg.pojo.User">
select * from mybatis.user where id=#{id};
</select>
<!-- 对象中的属性可以直接取出来,到具体的测试中再通过new User并传参来进行导入数据-->
<insert id="addUser" parameterType="com.xg.pojo.User">
insert into mybatis.user(id, name, pwd) values (#{id},#{name},#{pwd});
</insert>
<update id="updateUser" parameterType="com.xg.pojo.User" >
update mybatis.user set name = #{name},pwd=#{pwd} where id=#{id} ;
</update>
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id=#{id}
</delete>
</mapper>
测试 注意,只需要涉及数据变化的(增删改)全部都需要设置提交事务
public class UserMapperTest {
//查询所有用户
@Test
public void test(){
//第一步;获得sqlsession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
//第二步:执行sql
// 执行方式一:getMapper
// 我们需要把UserMapper的对象获取出来才能获取它的方法(getMapper)
//而getMapper就是通过接口的class对象来get的
/*sqlSession要想执行就必须要先getMapper获得sql,而sql在UserMapper的实现类UserMapper.xml中被实现了
所以我们只需要get接口就可以了,get接口之后就会返回接口类型,然后接口类型再执行不同的方法
* */
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();
//执行方式二:已过时,并且执行报错
//List<User> userList = sqlSession.selectList("com.xg.dao.UserMapper.UserMapper");
for (User user:userList) {
System.out.println(user);
}
//关闭sqlsession
sqlSession.close();
}
//查询固定id的用户
@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 i = mapper.addUser(new User(4, "hahaha", "787887"));
if (i>0){
System.out.println("数据插入成功");
}
//提交事务 CRUD时必须要加上提交事务
sqlSession.commit();
sqlSession.close();//固定代码
}
@Test //修改
public void updateUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.updateUser(new User(4, "疾风剑豪", "777777"));
if (i>0){
System.out.println("数据修改成功");
}
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();
}
}
易错点注意:
- 标签不要匹配错
- resource绑定mapper需要使用路径,也就是从"/" 来一级一级往下
- 程序配置文件必须要符合规范
- 空指针异常一般就是没有注册到资源
万能的Map
假如 我们的实体类或者数据库中的表,字段或者参数过多,我们应当考虑使用map集合来进行装填,来填装数据,不再非要依赖一个对象并将对象所有内容都传入
最新插入接口中的方法:
//万能的Map
int addUser2(Map<String,Object> map);//设置参数为map集合
xml实现:
<!-- 对象中的属性可以直接取出来,靠的就是传递map中的key-->
<insert id="addUser2" parameterType="map">
<!--只需要传递两个有用的参数就可以了,这里的参数#{userid},#{password}其实都是map的键,-->
insert into mybatis.user(id,pwd) values (#{userid},#{password});
</insert>
测试代码:
@Test //map插入
public void addUser2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("userid",4); //这里给map键值对赋值,使其与xml中的对应
map.put("password","898989");
mapper.addUser2(map);//然后将map传入
sqlSession.commit();
sqlSession.close();
}
使用Map传递参数时,直接在sql中取出key即可
parameterType=“map”
使用对象传递参数则是直接在sql中取出对象的属性 parameterType=“com.xg.pojo.User”(Object对象)
只有一个参数的情况下,可以直接设置基本类型然后在sql中获取 parameterType可以不写
模糊查询
java代码执行的时候传递通配符 %XXX% 其中"XXX"代表模糊查询的关键字
List<User> userList = mapper.getUserLike("%哥%");
在sql拼写中直接使用通配符:
select * from mybatis.user where name like "%"#{value}"%"
配置解析
核心配置文件
mybatis-config.xml
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
- configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名) 重点
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)
环境配置(environments)
MyBatis 可以配置成适应多种环境
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
但是要学会使用多套运行环境
mybatis的默认事务管理器就是JDBC,连接池:POOLED
属性(properties)配置
我们可以通过properties属性来实现引用配置文件
这些属性可以在外部进行配置,并可以进行动态替换,既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。【db.properties】
但是需要注意,所有的配置标签都需要按照先后顺序来进行排列
首先创建下面的配置文件
db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=truecharacterEncoding=UTF-8
username=root
passdword=981216
在核心配置文件标签中引入
<!-- 引入外部配置文件-->
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="981216"/>
</properties>
可以直接引入外部配置文件 resource=“db.properties”
也可以在标签中增加一些属性配置
注意,首先读取在properties标签元素体内的指定的属性,最后读取作为方法参数传递的外部配置文件属性,并覆盖之前读取过的同名属性,也就是说一切以外部配置文件属性为准
类型别名(typeAliases)设置
类型别名是为java类型设置一个短的名字(比如原来的:resultType=“com.xg.pojo.User” 现在改为User)
存在的意义仅在于用来减少长度,方便书写
如下
方法1:是直接换了一个名字(可自己随意diy)
<typeAliases>
<typeAlias type="com.xg.pojo.User" alias="User"></typeAlias>
</typeAliases>
在实体类比较少的时候建议使用第一种
如果实体类比较多,建议使用第二种,
第一种可以随意设置别名,第二种如果非要改别名需要在实体类上增加注解
@Alias("user")
public class User {...}//实体类
设置
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。
其他配置
typeHandlers(类型处理器)
- objectFactory(对象工厂)
plugins(插件):
mybatisPlus
映射器(mappers)
MappersRegistry:注册绑定Mapper文件
方法1:(推荐)
<mappers>
<mapper resource="com/xg/dao/UserMapper.xml"/>
</mappers>
方法2:使用class文件绑定
<mappers>
<mapper class="com.xg.dao.UserMapper"></mapper>
</mappers>
方式二注意点:
接口和它的Mapper配置文件(xml)必须同名
接口和它的Mapper配置文件(xml)必须在同一个包下
方式三:使用扫描包进行注入绑定
<mappers>
<package name="com.xg.dao"/>
</mappers>
方式三注意点:
接口和它的Mapper配置文件(xml)必须同名
接口和它的Mapper配置文件(xml)必须在同一个包下
mybatis生命周期和作用域
生命周期和作用域是至关重要的,因为错误的使用会导致非常严重的并发问题
SqlSessionFactoryBuilder
- 一旦创建了SqlSessionFactory,就不再需要他了
- 局部变量
SqlSessionFactory - 可以想象为 数据库连接池
- SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
- 最简单的就是使用单例模式或者静态单例模式。
SqlSession
可以理解为 连接到连接池的一个请求
SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。用完之后需要关闭,否则资源被占用
如上,每一个mapper都代表不同的业务
解决属性名和字段名不一致的问题
数据库中额字段为:
新建一个项目,拷贝之前的,测试实体类字段不一致的情况
public class User {
private int id;
private String name;
private String password;//此处原为 pwd
}
运行测试代码:
@Test
public void getUserList(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user= mapper.getUserById(1);
System.out.println(user);
sqlSession.close();
}
结果发现测试果然出现问题: 结果可以正常输出,但输出的密码框为null
因为xml配置文件中的sql语句问题
select * from mybatis.user where id=#{id};准确的写法应该是下面的代码,
select id,name,pwd from mybatis.user where id=#{id};
解决方法:
1,起别名 也就是把错误的密码从pwd再改为password
select id,name,pwd as password from mybatis.user where id=#{id};
2, resultMap 结果集映射
id,name,pwd //原数据库内容
id,name,password //想办法通过映射,将结果输出为这种
代码如下;
<!--结果集映射(返回)为User-->
<resultMap id="UserMap" type="User">
<!-- column代表数据库中的字段,property对应实体类中的属性 -->
<result column="id" property="id"></result>
<result column="name" property="name"></result>
<result column="pwd" property="password"></result>
</resultMap>
<!-- 这里resultMap="UserMap" 中的UserMap是引用上面的resultMap标签中id="UserMap"的内容-->
<select id="getUserById" resultMap="UserMap">
select * from mybatis.user where id=#{id};
</select>
resultMap 元素是 MyBatis 中最重要最强大的元素。
ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
在学习了上面的知识后,你会发现上面的例子没有一个需要显式配置 ResultMap,这就是 ResultMap 的优秀之处——你完全可以不用显式地配置它们
日志:
日志工厂
如果一个数据库操作出现了异常,我们需要排错,日志就是最好的助手
曾经:使用sout和debug来排错
现在:使用日志工厂
- SLF4J
- LOG4J (掌握)
- LOG4J2
- JDK_LOGGING
- COMMONS_LOGGING
- STDOUT_LOGGING (标准日志输出掌握)
- NO_LOGGING
在mybatis中具体使用哪一个日志实现,都是在mybatis核心配置文件中进行设定
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
Log4j
什么是Log4j
- Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件
- 我们也可以控制每一条日志的输出格式;
- 过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程
- 通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
Maven依赖包:
<!--导入log4j依赖-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
Log4j标准配置文件(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
#下面这行代码生成一个名为log的文件目录,里面的xg.log是日志记录
log4j.appender.file.File=./log/xg.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][%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为日志的实现
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
使用示范:
首先需要在使用log4j的类中导入包,一定要是 import org.apache.log4j.Logger;不能导错
生成日志对象,加载参数为当前类的class
static Logger logger = Logger.getLogger(UserMapperTest.class);
//因为其他测试方法可能也要调用,所以直接提升为全局变量
日志级别:
一般日志输出级别有很多,但是经常使用的只有
info debug error
logger.info("info:进入了testLog4j方法"); //相当于sout
logger.debug("debug:进入了testLog4j方法");
logger.error("error:进入了testLog4j方法");
分页
数据输出时进行分页可以减少数据的批次处理量,整体看起来更加简洁和整齐
limit分页
select * from user limit 1,2; #从下标为1的数据开始,一页两个
select * from user limit 3; #从下标为0的数据开始,一页三个
使用mybatis实现分页,核心就是sql
1,接口
//分页 使用万能的Map
List<User> getUserByLimit(Map<String,Integer> map);
Mapper.xml
<!-- 分页使用查询-->
<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
select * from mybatis.user limit #{startIndex},#{pageSize}
</select>
测试:
@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();
}
RowBounds分页:
过时,略
使用注解开发:
面向接口编程:
真正的开发中,很多时候多会选择使用面向接口编程
根本原因:
解耦,可扩展性,提高复用,分层开发中,上层不用管具体的体现,大家都遵守共同的标准,使得开发变得容易,规范性更好
在一个面向对象的系统中,系统的功能是由许许多多的不同对象来协作完成的,在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不南无重要了
而各个对象之间的协作关系则是系统设计的关键,小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是需要着重考虑的,这也是系统设计的蛀牙工作内容,面向接口编程就是指按照这种思想来编程的
关于接口的理解
-接口从更深层次的理解,应该是定义(规范,约束)与实现(名实分离的原则)分离
-接口的本社能反映了系统设计人员对系统的抽象理解
-接口应该有两类:
-第一类是对一个个体的抽象,他可对应为一个抽象体(abstract class)
-第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface)
-一个个体有可能有多个抽象面,抽象体与抽象面是有区别的
三个面向的区别:
面向对象是指,我们考虑问题时,以对象为单位,考虑他的属性以及方法,
面向过程是指,问考虑问题时,以一个具体的过程(事务过程)为单位,考虑他的数显
面向接口编程:接口设计与非接口设计是针对复用技术而言的额,与面向对象(过程)不是一个问题,更多的体现就是对系统整体的架构
使用注解开发:
1,注解在接口上实现:
@Select("select * from user")
List<User> getUsers();
2,需要在核心配置文件中绑定接口
<!-- 绑定接口-->
<mappers>
<mapper class="com.xg.dao.UserMapper"></mapper>
</mappers>
经debug可知,使用注解的底层原理就是反射
注解的本质就是反射机制的实现
底层则是利用动态代理来进行实现
注解实现CRUD
Mybatis执行流程图:
已知我们可以在工具类创建的时候设置变量为true,从而实现自动提交事务,但是如果代码有问题是也会提交成功
关于@Param()注解:
基本类型的参数或者String类型需要加上
引用类型不需要添加
如果只有一个基本类型的话,可以忽略,但是建议加上
我们在sql引用的就是@Param中()内设定的属性
ps: #{}与$ {}的区别,#{}可以很大程度上防止sql注入问题
而 $ {}无法防止sql注入问题
编写解耦,增加注释:(注意这里的@Param)
public interface UserMapper { //接口中每一个方法都要表明返回的类型
@Select("select * from user")
List<User> getUsers();
//方法存在多个参数时,所有的参数前面必须加上@Param("id")
@Select("select *from user where id=#{id}") //注意基本类型才需要使用@Param("id")
User getUserById(@Param("id") int id); //引用类型不需要写@Param
//这里传入的参数以@Param("id")中的id为准,
// xml实现中的字段也必须与这里的一致,比如注解中参数为id xml中参数为 id2 就会报错,必须保持一致
@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=#{id}")
int deleteUser(@Param("id") int id);
}
测试类:
记得如果有新的接口则必须将新街口注册绑定到核心配置文件中
<!-- 绑定接口-->
<mappers>
<mapper class="com.xg.dao.UserMapper"></mapper>
</mappers>
</configuration>
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
//底层主要应用反射
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
/* List<User> users = mapper.getUsers();
for (User user : users) {
System.out.println(user);
}*/
//查找用户
/* User userById = mapper.getUserById(1);
System.out.println(userById);*/
//增加用户
/*int i = mapper.addUser(new User(5, "深超", "4545454"));
System.out.println(i);*/
//修改用户
/* mapper.updateUser(new User(4,"adc","777777"));*/
//删除用户
mapper.deleteUser(5);
sqlSession.close();
}
Mybatis详细执行流程
经过deBug可以发现mybatis有以下执行流程:
Lombok
一款使用注解简化代码的插件,使用时需要先在IDEA中下载插件安装好之后再进行导入依赖使用
依赖如下:
<!-- lombok依赖-->
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
本身携带的注解有不少,但是最经常使用的只有三个注解:
@Data get/set/toString/hashcode/equals
@AllArgsConstructor 有参构造
@NoArgsConstructor 无参构造
多对一查询:
多个学生对应一个老师
对于学生这边而言,多个学生关联一个老师 (关联)
对于老师而言,一个老师关联多个学生 (集合)
环境搭建:
首先创建一个表格:
CREATE TABLE teacher (
id INT NOT NULL,
NAME VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO teacher(id,NAME) VALUES (1,'老秦');
CREATE TABLE student(
id INT NOT NULL,
NAME VARCHAR(20) DEFAULT NULL,
tid INT DEFAULT NULL,
PRIMARY KEY(id),
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');
INSERT INTO student (id,NAME,tid) VALUES (2,'钢盆','1');
INSERT INTO student (id,NAME,tid) VALUES (3,'大红眼子','1');
INSERT INTO student (id,NAME,tid) VALUES (4,'小剑魂子','1');
INSERT INTO student (id,NAME,tid) VALUES (5,'小果果','1');
测试环境搭建:
1,导入lombok
2,新建pojo实体类 Teacher ,Student并@Data
3,建立Mapper接口
4,建立Mapper.xml文件
5,在核心配置文件中绑定注册我们的Mapper接口或者文件
<mappers>
<mapper class="com.xg.dao.TeacherMapper"></mapper>
<mapper class="com.xg.dao.StudentMapper"></mapper>
</mappers>
最后测试查询是否成功(✔)
按照查询嵌套处理
<!-- 多对一查询:
思路:
1,查询所有的学生
2,根据查询出来的学生的tid寻找对应的老师 类似于子查询 在已经查询出来的结果中再查询一次
-->
<select id="getStudent" resultMap="StudentTeacher">
select * from student;
</select>
<resultMap id="StudentTeacher" type="Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!-- 复杂的属性,我们需要单独处理 是对象了就用association 如下 集合就用:collection -->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
<!-- <collection property=""/>-->
</resultMap>
<!--此处的select id="getTeacher"对应上面那句select="getTeacher"
也就是获取上面column="tid"中tid的值作为id=#{id};中#{id}的值,
并用这个值作为条件查询一遍teacher表,
最后将查询出来的表作为对象与原本在student表中查询出来的结果放在一起(或者说是一张表中)
-->
<select id="getTeacher" resultType="Teacher">
select * from teacher where id=#{id};
</select>
按照结果嵌套处理:
思路:
先写sql,将所需数据都获取到,
再先将两个正常的数据获取:< result property=“id” column=“sid”/>
剩下的对象类型也先写上,使其成为一个包含Teacher对象的新表,
然后通过< result property=“name” column=“tname”/>
将javaType="Teacher"中Teacher表的name映射为tname
最后赋值给新表中对象的具体数据
<select id="getStudent2" resultMap="StudentTeacher2">
select s.id sid,s.name sname,t.name tname,t.id tid
from student s,teacher t
where s.tid=t.id;
</select>
<resultMap id="StudentTeacher2" type="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
<result property="id" column="tid"/>
</association>
</resultMap>
一对多处理:
一个老师拥有多个学生,对老师来演就是一对多的关系
环境搭建同多对一一样
学生:
@Data
public class Student {
private int id;
private String name;
//学生需要关联一个老师
private int tid;
}
老师:
@Data
public class Teacher {
private int id;
private String name;
//一个老师拥有多个学生(的集合)
private List<Student> students;
}
两种方法的接口:
//按照结果查询获取指定老师下的所有学生及老师的信息
Teacher getTeacher(@Param("id") int id);
//另外一种方法
Teacher getTeacher2(@Param("tid") int id);
按照结果嵌套处理:
<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};
-- 查询的条件为学生的tid等于老师的id,并且老师的id等于输入的id
</select>
<resultMap id="TeacherStudent" type="Teacher"> <!--类型是Teacher-->
<result property="id" column="tid"/> <!--前面两个正常赋值-->
<result property="name" column="tname"/>
<!--复杂的属性,我们需要单独处理 是对象用association 集合就用:collection -->
<!-- 因为老师有一个学生对象的集合,所以用collection
之前的javatype是指定属性的类型
集合中的泛型信息,我们要使用ofType获取
所以ofType="Student"就代表类型为Student-->
<collection property="students" ofType="Student">
<result property="id" column="sid"/> <!--然后就是挨个拼装赋值-->
<!-- property代表查询出的中Student对象列的名字, column代表列的内容-->
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
如下为测试方法:
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacher(1);
System.out.println(teacher);
sqlSession.close();
/*
* Teacher(id=1,
* name=老秦,
* students=[Student(id=1, name=亚索, tid=1),
* Student(id=2, name=钢盆, tid=1),
* Student(id=3, name=大红眼子, tid=1),
* Student(id=4, name=小剑魂子, tid=1),
* Student(id=5, name=小果果, tid=1) ])
* */
}
按照查询嵌套处理:
<select id="getTeacher2" resultMap="TeacherStudent2">
select * from mybatis.teacher where id=#{tid}
</select>
<resultMap id="TeacherStudent2" type="Teacher">
<!-- 注意,因为上面的父查询语句已经写了select * from,所以只用专注写子查询就好了
再次注意,因为一对多,对应的是一个Java类集合ArrayList ,而集合中的泛型信息又是Student,所以如下:-->
<collection property="students" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId" column="id"/>
</resultMap>
<select id="getStudentByTeacherId" resultType="Student">/*返回的每一个对象就是学生*/
select * from mybatis.student where tid=#{test}
</select>
小结:
1,关联-association (多对一)
2,集合-collection(一对多)
3,javaType & ofType
1,javaType用来指定实体类中属性的类型
2,ofType 用来指定映射到List或者集合中的pojo类型,也就是泛型中的约束类型
需要注意的是:
要保证sql的可读性,保证通俗易懂
注意一对多和多对一中属性名和字段的问题
如果出了问题不容易排查,可以搭配日志使用
面试高频:
- Mysql引擎
- InnoDB底层原理
- 索引
- 索引优化
动态sql
什么是动态sql: 动态sql就是指根据不同的条件生成不同的sql语句
借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少:
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
创建一个基础工程
1,导包
2,编写配置文件
3,编写实体类
@Data
public class Blog {
private int id;
private String tltle;
private String author;
private Date createTime;
private int views;
}
4,编写实体类对应Mapper接口和Mapper.xml配置文件
if
配置文件:
<select id="queryBlogIF" parameterType="map" resultType="blog">
/*后面指定参数类型parameterType 和返回值类型resultType*/
select * from mybatis.blog where true
/*注意,因为后面要跟条件判断,并且条件不足时本身sql也要成立
所以必须要加上一个where true*/
<if test="title !=null">
and title = #{title}
</if>
<if test="author != null">
and author=#{author}
</if>
</select>
测试代码:
@Test
public void queryBlogIF(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
map.put("title","java是如此的简单");
map.put("author","老秦说java");
List<Blog> blogs = mapper.queryBlogIF(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
choose(when,otherwise)
注意,这里的多个when条件只能有一个成立,第一个成立之后就无视后面的when和otherwise中的语句 如果两个都不成立,则执行otherwise标签中的语句,
<select id="queryBlogChoose" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
/*注意,这里的多个when条件只能有一个成立,第一个成立之后就无视第二个条件和otherwise中的语句
如果两个都不成立,则执行otherwise标签中的语句,
*/
<choose>
<when test="title !=null">
title = #{title}
</when>
<when test="author != null">
author=#{author}
</when>
<otherwise>
views=#{views}
</otherwise>
</choose>
</where>
</select>
trim(where,set)
trim标签就是自定义更改字段,比较有实际意义的是下面两个代码
where标签
<select id="queryBlogChoose" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
/*加上一个where标签比直接在sql语句中声明where true更加完善(专业)
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。
而且,若子句(where后面的条件)的开头为 “AND” 或 “OR”,where 元素也会将它们去除。*/
<if test="title !=null">
title = #{title}
</if>
<if test="author != null">
and author=#{author}
</if>
</where>
</select>
set标签
只有下面两个作用
set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)
<update id="updateBlog" parameterType="map">
update mybatis.blog
<set>
<if test="title !=null">
title=#{title},
</if>
<if test="author !=null">
author=#{author},
</if>
</set>
where id=#{id}
</update>
所谓的动态sql,本质上还是sql语句,只是我们可以在sql层面 去执行一个逻辑代码
SQL片段
有时候我们可能会将一些可以共用的部分抽取出来进行二次使用,具体的方法有:
使用sql标签抽取公共部分:
<sql id="if-title-author">
<if test="title !=null">
title = #{title}
</if>
<if test="author != null">
and author=#{author}
</if>
</sql>
在需要使用得地方使用Include标签引用即可
引用时使用< include refid=“id”>对应要引用的sql片段得id就可以
<select id="queryBlogIF" parameterType="map" resultType="blog">
/*后面指定参数类型parameterType 和返回值类型resultType*/
select * from mybatis.blog
<where>
<include refid="if-title-author"></include>
</where>
</select>
注意事项:
最好基于单表来定义sql片段
sql片段不能存在where标签
foreach
select * from user where 1=1 and
<foreach item="id" collection="list"
open="(" separator="or" close=")">
#{id}
</foreach>
(id=1 or id=2 or id=3)
官方文档解释如下:
接口及需求:
//查询1,2,3号记录
List<Blog> queryBlogForeach(Map map);
测试xml配置文件:
<!-- sql语句思路: select * from blog where and (id=1 or id=2 or id=3)
我们现在传递一个万能的map,这个map中可以存在一个集合
-->
<!--注意#{id}里面的id是从map集合中遍历出来的-->
<select id="queryBlogForeach" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id=#{id}
</foreach>
</where>
</select>
测试代码:
@Test
public void queryBlogForeach1(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
/*思路:先创建一个map来储存参数,再创建一个list集合储存要查询的id、
* 并把list集合放在map集合中,将map集合传递进去
* */
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);
sqlSession.close();
}
动态sql就是在拼接sql语句,我们只要保证sql的正确性,按照sql的格式,去排列组合就可以了
建议先在mysql中写出完整的sql,再对应的去修改成为我们的动态sql 实现通用即可
缓存
所有的查询都需要连接数据库,这样十分的消耗资源,解决思路就是一次查询的结果可以将其暂存在一个可以直接获取的地方,也就是暂时放在内存中,这就叫缓存
这样当我们再次查询相同数据的时候,直接走缓存就好,不用继续走数据库了
什么是缓存?缓存的优点?
缓存就是存在内存中的临时数据
将用户经常查询的数据放在缓存(内存)中,用户去查询就不用从磁盘上(关系型数据库数据文件)查询,变为从已经加载过的缓存中查询,从而提高查询效率,提高了解决并发系统的行能问题
PS:三高指的说就是 高并发 高可用 高性能
为什么使用缓存?
减少和数据库的交互次数,减少系统开销,提升系统效率
什么样的数据最适合使用缓存?
经常查询并且不经常改变的数据
Mybatis缓存
Mybatis包含一个非常强大的查询缓存特性,他可以非常方便的定制和配置缓存,缓存可以极大地提升查询效率
Mybatis系统默认定义了良机缓存:一级缓存和二级缓存
默认情况下,只有一级缓存开启,sqlSession级别的缓存,也被称为本地缓存
二级缓存需要手动开启和配置,是基于namespace级别的缓存
为了提高扩展性Mybatis定义了缓存接口Cache接口,我们可以通过实现接口来自定义二级缓存
一级缓存
一级缓存也叫做本地缓存:
与数据可同义词,会话弃件查询到的数据会放在本地缓存中
如果以后想要获取相同的数据,可以直接从缓存中获取,没有必要再去查询数据库
测试一级缓存:
1,开启日志!
2,测试在一个Session中查询两次记录
3,查看日志输出
一级缓存失效的情况:
查询不同的数据
增删改操作可能会改变原来的数据,所以必定会刷新缓存
查询不同的Mapper.xml
手动清理缓存
sqlSession.clearCache(); //手动清理缓存
小结:
一级缓存默认是开启的(其实也无法关闭),只在一次sqlsession中有效,也就是从拿到连接到关闭连接这个区间段
一级缓存相当于一个Map集合
二级缓存:
二级缓存也叫做全局缓存.一级缓存的作用域太低了,所以也就诞生了二级缓存
基于namespace级别的缓存,一个名称空间,对应一个二级缓存
工作机制:
一个会话查询一条数据,这个数据就会放在当前会话的一级缓存中;
如果当前会话关闭了,这个会话对应的一级缓存就没有了,但是我们想要的是会话即便关闭了,一级缓存的数据还会被保存到二级缓存中.
新的会话查询消息就可以存二级缓存中获取内容
步骤;
1,开启全局缓存:
<!--显式的开启全局缓存-->
<setting name="cacheEnabled" value="true"/>
2,在要使用二级缓存的Mapper中开启
<!-- 在当前Mapper.xml中使用二级缓存,可以直接使用单纯的标签,也可以在下面自定义参数-->
<cache/>
<!-- eviction="FIFO"代表使用FIFO的输入输出策略
flushInterval="600000"代表每间隔60秒刷新一次缓存
size="512"代表最多只能存512个缓存
readOnly="true"就代表只读了 -->
<cache eviction="FIFO"
flushInterval="600000"
size="512"
readOnly="true"/>
测试:
代码如下:(如果只用一级缓存就会报错执行器已经关闭 Executor was closed)
//二级缓存测试
@Test
public void test(){
//首先创建两个sqlsession
SqlSession sqlSession = MybatisUtils.getSqlSession();
SqlSession sqlSession2 = MybatisUtils.getSqlSession();
//再利用两个不同的sqlSession 去 getMapper并执行方法
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
sqlSession.close();
User user2 = mapper2.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2); //true
sqlSession2.close();
}
注意:开启二级缓存时我们需要将实体类实例化
public class User implements Serializable {}
否则会爆序列化异常:
Caused by: java.io.NotSerializableException: com.xg.pojo.User
小结:
只要开启了二级缓存,在同一个Mapper下就有效
所有的数据都会先放在一级缓存中,只有当会话提交或者关闭的时候才会提交到二级缓存
Mybatis缓存原理:
用户查询数据流程:
首先查看二级缓存中有没有,
再查看一级缓存中是否存在
最后才会执行数据库
自定义缓存 - ehcache
现在已经出来了一个代替ehcache的软件,就是redis,现在只需要了解一下就行了
Ehcache是一种广泛使用的开源java分布式缓存,主要面向通用缓存
要想在程序中使用ehcache,要先导包:
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.0</version>
</dependency>
在Mapper中指定使用我们的ehcache
<!-- 如下为自定义缓存-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
至此
完结
这只是一个懵懂者的蹒跚学步