Mybatis入门
**Mybatis-3.5.2入门**自己在跟着狂神学习Mybatis整理的笔记,第一次做,很多不足点可能,分享出去也希望大家能给我更多的建议,大家一起学习。!一、基本配置需要环境:①jdk1.8②mysql5.7③maven3.6.3④IDEA需要的基层知识①JDBC②Mysql③Java基础④Maven⑤Junit单元测试学习Mybatis配置文件最好方法:看官网mybatis.org/myb
**
Mybatis-3.5.2入门
**
自己在跟着狂神学习Mybatis整理的笔记,第一次做,很多不足点可能,分享出去也希望大家能给我更多的建议,大家一起学习。!
一、基本配置需要
环境:
①jdk1.8
②mysql5.7
③maven3.6.3
④IDEA
需要的基层知识
①JDBC
②Mysql
③Java基础
④Maven
⑤Junit单元测试
学习Mybatis配置文件最好方法:看官网mybatis.org/mybatis-3/index.html
二、简介
2.1、什么是mybatis
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。
①MyBatis 是一款优秀的持久层框架,
②它支持自定义 SQL、存储过程以及高级映射
③MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作
④MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain ⑤Old Java Objects,普通老式 Java 对象)为数据库中的记录.
2.2、如何获取Mybatis
①maven仓库:https://mvnrepository.com/artifact/org.mybatis/mybatis
这里拿3.5.2的依赖举例
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
②github:
获取框架及源码:https://github.com/mybatis/mybatis-3/releases
源码中文:https://github.com/tuguangquan/mybatis/releases/tag/mybatis-3.2.8
③中文文档:https://mybatis.org/mybatis-3/index.html
2.3、持久层
①数据持久化
·持久化就是将程序的数据在持久状态和瞬时状态转化的过程
·内存:断电即失
·持久状态:数据放在数据库里,只要不主动删除,数据一直存在例如数据库(jdbc)、io文件持久化,生活中的冷藏品都是持久化的应用
·瞬时状态:在断电或者其他情况下
为什么需要持久化:
一、有一些对象,绝对不能让他丢失,例如你支付宝的钱
二、内存太贵了
②持久层:
·完成持久化工作的代码块,类似dao层
·层界限是非常明显的,就比如dao层完成持久化的操作,而serivice层就是一个业务逻辑的操作,是调用dao层的,紧接着controller层接收用户请求转发给(调用)service,并且回应用户给前端,每层都有自己的业务去做,这样的层次界限非常明显,而不是混杂的。
·持久层可以将业务数据存储到磁盘,具有长期存储能力,只要磁盘不损坏,在断电或其他情况下,重新开启系统仍然可以读取这些数据。而这就是一个持久化的过程
·持久层可以使用巨大的磁盘空间,廉价,但是缺点就是慢,这个慢相对于内存而言,一般系统运行一般也感觉不到,但是对于比方双十一庞大的数据量,每秒都需要处理成千上万数据量,慢就会带来巨大代价,极有可能宕机,这是公司不能承受的。这就是持久层的缺点。
③为什么选mybatis
·帮助程序员将数据存入数据库中
·方便
·传统JDBC代码太复杂了,mybatis框架的出现极大的简化了jdbc的代码
·自动化,你只需要按照它的步骤去做,它就能帮你自动的完成以往jdbc工作,甚至还完成的更好,懒人福音。
·优点:
。简单易学
。灵活
。sql和代码的分离,提高了可维护性
。提供映射标签,支持对象与数据库的orm字段
。提供对象关系映射标签,支持对象关系组建维护
。提供xml标签,支持编写动态sql。
。使用的人多,适应时代,spring,springMVC,springboot都是这样
三、第一个Mybatis程序
思路:搭建环境–》导入Mybatis–》编写代码–》测试
3.1搭建环境
①搭建数据库
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL,
`name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`pwd` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `user` VALUES (1, '黄老', '123456');
INSERT INTO `user` VALUES (2, '孟老', '123');
INSERT INTO `user` VALUES (3, '邵老', '1234');
SET FOREIGN_KEY_CHECKS = 1;
②新建项目
·创建一个普通的maven项目
·删除src,这样就可以把它当作一个父工程看待,也是为了在里面建立更多子工程便于学习
·导入依赖
<dependencies>
<!--mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<!--mysql-connector依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- junit依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
3.2创建一个模块
①在我们的父工程右键新建一个子工程mybatis_01
·这样的好处是不用重复导包,因为它的父工程已经把这事做完了
·子工程只需要导入自己要用的包就行,这个咱们在讲maven已经做完了
②编写mybatis核心配置文件
即xml配置文件,咱们取名一般统一为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核心配置文件-->
<configuration>
<!--环境,我们可以做出多个环境,例如开发,测试等等-->
<environments default="development">
<environment id="development">
<!--事务管理,默认使用JDBC-->
<transactionManager type="JDBC"/>
<!--数据源-->
<dataSource type="POOLED">
<!--jdbc驱动-->
<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="yourUsername" value="root"/>
<property name=yourPwd" value="studentm200033"/>
</dataSource>
</environment>
</environments>
</configuration>
我们来解释一下
·表示里面的内容为核心配置文件
·表示环境,这里描述的是数据库
我们可以做出多个环境,例如开发,测试等等
·表示默认环境为开发环境
·事务管理,默认为jdbc
·代表jdbc驱动
·代表mysql的url,即连接数据库的url,这里我们可以通过IDEA的mysql工具获取
再解释一下&这个其实代表&的转义符,因为在xml配置url光用&会报错
useSSL=true:用户的安全连接
useUnicode=true&characterEncoding=UTF-8:指定字符编码格式为UTF-8
③编写mybatis工具类
·每个基于 MyBatis 的应用都是以一个 SqlSessionFactory(使用的是工厂模式) 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。这里强力建通过xml配置文件去创建SqlSessionFactory实例,因为通过XML创建的形式,信息在配置文件中,有利于维护和修改,而拿到了SqlSessionFactory实例后,我们可以从中获得 SqlSession 的实例。SqlSession(类似JDBC的connector对象) 提供了在数据库执行 SQL 命令所需的所有方法。(类似jdbc以前的preparedStatement)你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。Sqlsession不是线程安全的,因此不能共享,而sqlSession用完必须关闭,因此我们经常把它放在finally代码块中
·通过xml获取SqlSessionFactory
String resource = “org/mybatis/example/mybatis-config.xml”;
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
通过resource读取一个xml配置文件,并把它加载成流
然后通过SqlSessionFactoryBuilder的bulid()方法获取SqlSessionFactory实例
而读取配置文件我们一般可以将它封装为一个工具类,
代码如下:
package com.mbw.dao.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;//将sqlSessionFactory作用域改为全局
static {//让它一开始就加载,不用再管理复杂路径
try {
String resources="mybatis-config.xml";//定义配置文件
//将配置文件放入流中
InputStream inputStream= Resources.getResourceAsStream(resources);
//加载流获取SqlSessionFactory对象,这是使用mybatis的第一步
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
·获取SqlSession
SqlSession是Mybatis的核心接口,SqlSession的作用类似于一个JDBC中的Connection对象,代表着一个连接资源的启用
它可以获取Mapper接口,发送sql给数据库,控制数据库事务
我们可以通过SqlSeesionFactory去获取它,代码如下:
public static SqlSession getSqlSession{
//通过openSession获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
}
至此,工具类建造完毕
3.3编写代码
①编写实体类
package com.mbw.pojo;
public class User {
//实体类
//属性要和你自己的数据库字段名一致
private int id;
private String name;
private String pwd;
public User() {
}
public User(int id, String name, String pwd) {
this.id = id;
this.name = name;
this.pwd = pwd;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", pwd='" + pwd + '\'' +
'}';
}
}
②Dao(Mapper)接口
package com.mbw.dao;
import com.mbw.pojo.User;
import java.util.List;
public interface UserDao {//这里的DAO实际上等价于之后要说的Mapper,之后我们都用mapper
List<User> getUserList();
}
③Mapper.xml(由原来的UserdaoImpl)转变为一个Mapper
·表面上这里本来是实现类,其实在mybatis就不用了
·我们通过映射器Mapper,映射器的主要作用就是将SQL查询到的结果映射为一个实体类,或者说将实体类的数据插入到数据库中,并定义一些关于缓存的重要内容
·Mapper分为两类,接口和XML
·与之前的接口实现类做对比
1)实现类要实现dao接口,而xml是通过绑定接口
2)实现类重写方法是直接实现方法的形式
Xml是通过一个个标签重写方法,而标签的id对应要重写的方法名
3)实现类要把执行sql的语句的准备工作,sql部分全部写完
xml只需在标签写sql语句
·resultType:用来表明返回一个结果,内容要写全域名,而返回的类型我们写的是POJO实体类的类型,亦或是一个结果集的泛型
·parameterType:参数类型,比方你接口的方法需要传参数,那么就必须在xml中写上这个属性,指定你的参数类型
比方接口中有这样一个方法(通过id去查询指定用户):
User getUserById(int id);
那么xml去写标签(sql语句实现该方法),就比上面的多一个
·#{id}代表接收方法传输的id参数(变量)值
select * from mybatis.user where id = #{id};
在这个xml文件中参数类型就指定了int
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="com.mbw.dao.UserDao">
<!-- select查询语句-->
<select id="getUserList" resultType="com.mbw.pojo.User">
select * from mybatis.user;
</select>
</mapper>
3.4Junit测试
注意点1:
org.apache.ibatis.biingException: Type interface com.mbw.dao.MapperDao is not known to the MapperRegistry.
MapperRegistry:在mybatis核心配置文件中注册mapper
类型接口dao未在Mapper注册中心中注册
解决方法:在mybatis核心配置文件中注册(通过全域名,mapper.xml文件)
<mappers>
<mapper resource="com/mbw/dao/UserMapper.xml"/>
</mappers>
注意点2:
系统找不到mapper.xml,但确实写了
原因:maven由于约定大于配置,因为我们现在的mapper.xml不是写在resources下,而是src/main/java下,所以系统当然找不到
解决方案:在pom.xml中的build中配置resources,来防止我们的资源导出失败的问题
<build>
<!--在build中配置resources,来防止我们资源导出的问题-->
<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>
·测试的包尽量和开发的包一一对应
①获取sqlSession对象
直接使用工具类中的getsession方法即可
②通过sqlSession.getMapper获取自己的mapper接口对象
·getMapper只是第一种方式,还有一种
sqlSession.直接接sql语句(过时)
List userList1 = sqlSession.selectList(“com.mbw.dao.MapperDao.getUserList”);
我们把它和实现sql的Mapper.xml作对比
·sqlSession.selectList和xml的select标签作用一样,都是select
·而xml的id绑定了接口方法, sqlSession.selectList()中的参数也是绑定接口方法
·xml中resultType专注绑定方法的返回值类型,这里是user
sqlSession.selectList()的返回值类型是object型列表,做一个强转后一样也是user类型
这样我们就能理解第二种方式的用意了
但是这样的代码并不整洁清晰安全,而且容易犯易错在字符串字面值以及强制类型转换上面
③通过接口执行方法,而这个方法在xml已被重写为相应的sql执行方法
例如本例子中的.getUserList()方法,咱们再xml实际上让这个方法与select绑定
就是重写这个方法,执行查询操作
代码如下:
package com.mbw.dao;
import com.mbw.dao.Utils.MybatisUtils;
import com.mbw.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class MapperDaoTest {
@Test
public void test(){
//获取sqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
//执行sql
//方式一:getMapper
try{
MapperDao mapper = sqlSession.getMapper(MapperDao.class);
List<User> userList = mapper.getUserList();
for(User user:userList){
System.out.println(user);
}
}catch (Exception e){
e.getStackTrace();
}finally {
//关闭sqlSession
sqlSession.close();
}
//方式二:sqlSession.直接接sql语句(过时)
// List<User> userList1 = sqlSession.selectList("com.mbw.dao.MapperDao.getUserList");
// for(User user:userList1){
// System.out.println(user);
// }
/*
我们把它和实现sql的Mapper.xml作对比
·sqlSession.selectList和xml的select标签作用一样,都是select
·而xml的id绑定了接口方法, sqlSession.selectList()中的参数也是绑定接口方法
·xml中resultType专注绑定方法的返回值类型,这里是user
sqlSession.selectList()的返回值类型是object型列表,做一个强转后一样也是user类型
这样我们就能理解第二种方式的用意了
但是这样的代码并不整洁清晰安全,而且容易犯易错在字符串字面值以及强制类型转换上面
*/
}
}
④run
整理一遍思路:
第一步搭建环境:连接数据库,导入依赖:mysql,mybatis,sql-connector,顺便在build中把resource配置了
第二步:创建一个模块:
·核心配置文件:指定环境,开发、测试。。,事务管理,代表jdbc
代表数据源,pooled代表连接池资源
表示数据库相关驱动,如driver,url,username,pwd
注册一个mapper
·通过核心配置文件赋值resource,放进流后通过sqlSessionFactoryBuilder的build方法获取sqlSessionFactory,然后通过sqlSessionFactory的opensession去”生产”
一个session
第三步:写代码
·实体类创建
·写mapper,mapper由接口和XML组成,先写接口,里头写要实现的方法
接着xml,xml通过namespace绑定刚才写的接口,之后就是sql语句了,sql语句的id与接口方法名绑定,记住标签体就是和sql有关,而里面的返回值类型就专注你的返回类型即可
第四步:测试
·获取sqlSession
·通过sqlSession的getmapper(里头是接口.class(动态代理,先不管这个))获取指定接口的mapper、
·mapper调用实现接口相应方法
·关闭sqlsession
四、CRUD
回顾:搭建框架中的变与不变
·不变:实体类(针对每一个项目来说),和工具类(获取SqlSessionFactory和SqlSession)
·变:
①Mapper接口:你要不断去增加里面的方法
②Mapper.xml:
·nameSpace:就是绑定接口,你的接口名在变,你的xml只要想绑定这个接口,nameSpace就必须得变
·标签,因为要实现接口的方法,你的方法在增加,对应实现的方法标签也得增加,功能也得不断跟着改变,(增删改查。。。。),而id也随着你的接口方法名变化,resultType则根据你的sql语句的返回值来跟进
③测试类:方法变了,你当然测试方法得跟着变了
4.1增加用户(c)
①在Mapper接口中增加addUser方法用来增加用户,在这里有一点需要注意的是,根据insert语句以及User的属性有3个,我们addUser本来需要传User的3个属性,但因为同样换种说法是增加用户,我们同样可以将User对象作为参数传参,这样就解决了参数过多的问题。
int addUser(User user);
②在Mapper.xml文件中增加insert标签,id绑定方法名,parameterType为User对象
中间写入insert语句
<insert id="addUser" parameterType="com.mbw.pojo.User" >
insert into mybatis.user (id,name,pwd) values (#{id},#{name},#{pwd});
</insert>
③测试
通过mapper对象实现addUser方法传入user对象3个参数
再通过查询方法查询。代码如下:
mapper.addUser(new User(4,"大佬","1234567"));
sqlSession.commit();//提交事务
List<User> userList = mapper.getUserList();
System.out.println();
for(User user:userList){
System.out.println(user);
}
注意点:增删改必须提交事务
sqlSession.commit();//提交事务
不然代码是运行成功了,但你数据库并没有增加用户
4.2更新用户(U)
①在Mapper接口中增加UpdateUser方法用来增加用户,和addUser一样,传入User对象类型参数
int UpdateUser(User user);
②在Mapper.xml文件中增加update标签,id绑定方法名,parameterType为User对象
中间写入update语句
<update id="UpdateUser" parameterType="com.mbw.pojo.User">
update mybatis.user set name=#{name},pwd=#{pwd} where id =#{id};
</update>
③测试
通过mapper对象实现UpdateUser方法传入user对象3个参数
通过返回的int判断有没有修改成功
int res = mapper.UpdateUser(new User(4, "刘老", "1234567890"));
if(res>0){
System.out.println("更新成功");
}
sqlSession.commit();//提交事务
4.3删除用户(D)
①在Mapper接口中增加中增加deletsUser方法用来增加用户,通过id删除指定用户,所以传入id参数
int deleteUser(int id);
②在Mapper.xml文件中增加delete标签,id绑定方法名,parameterType为int,中间写入delete语句
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id=#{id};
</delete>
③测试
通过mapper对象实现deleteUser方法传入id删除指定用户
通过返回的int判断有没有修改成功
int res = mapper.deleteUser(4);
if(res>0){
System.out.println("删除成功");
}
sqlSession.commit();//提交事务
4.4Map和模糊查询
①Map
当我们比方查询数据,插入数据,修改数据,如果按传统方法来,把实体对象当参数传入,如果对象的属性特别多,就会特别麻烦,这个时候我们可以采用Map当参数传入。
以Insert为例子
·在Mapper接口增加一个新的insert方法,传入参数变为map<String,Object>
int addUser2(Map<String,Object> map);
·在Mapper.xml中添加Insert标签
在这里就开始体现Map对象的属性名一一对照,这样就造成麻烦,而map的键名我们可以随意取
例如这样:
<insert id="addUser2" parameterType="map" >
insert into mybatis.user (id,name,pwd) values (#{userId},#{Username},#{password});
</insert>
标红的地方在这里不是报错哦,是否发现标红的地方和以往插入的不一样呢,也就是传入的参数名我们可以不用和对象属性名一样。
·Test
@Test
public void addUser2(){
SqlSession sqlSession=MybatisUtils.getSqlSession();
MapperDao mapper = sqlSession.getMapper(MapperDao.class);
Map map= new HashMap<String, Object>();
map.put("userId",10);
map.put("Username","孟博文");
map.put("password",123098);
mapper.addUser2(map);
sqlSession.commit();
sqlSession.close();
}
这里注意的是map里面放入的值的key必须和你xml中传入的参数名一致(即刚刚标红的key名)
·假设,我们的实体类,或者数据库的表,字段或者参数过多,我们应当考虑使用map
·Map传递参数,直接在sql中取出key即可,大家可以到时候项目中数据库的字段很多时就可以体会到Map的好处,因为map的KEY你想怎么取就怎么取。
而咱们对象传递参数,直接在sql中取对象的属性即可
·只有一个基本类型参数的情况下,可以直接在sql中取到
·多个参数用map,或者注解(后面会说)
②模糊查询
·Java执行的时候,传递通配符% %
一样的,dao接口添加方法,参数传入String Value(要查询的值)
·在Mapper.xml中增加相应标签
<select id="getUserByLike" resultType="com.mbw.pojo.User">
select * from user where name like #{value}
</select>
注意点:sql注入问题
我的sql语句也可以这样写,即在sql拼接中使用通配符
select * from user where name like “%”#{value}"%"
这样或许也能查出来,但是对于业务而言,攻击者很容易通过sql注入查询到数据库的信息,就会造成数据泄露等严重问题,所以我们最好就是把它做绝,写死,把两个百分号去掉,而在代码中写入
·Test
@Test
public void GetUserByLike(){
SqlSession sqlsession=MybatisUtils.getSqlSession();
MapperDao mapper = sqlsession.getMapper(MapperDao.class);
List<User> res = mapper.getUserByLike("%孟%");
for (User re : res) {
System.out.println(re);
}
sqlsession.close();
}
我们在括号中传入模糊查询需要的% %,
通过这样的方式,我们可以很好地减少sql注入的可能。
五、配置解析
5.1核心配置文件
·mybatis-config.xml,核心配置文件名尽量取这个
·MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。所以mybatis讲究每一个标签的顺序,且相对固定,配置文档的顶层结构如下:
5.2环境配置(environments)
·MyBatis 可以配置成适应多种环境
·尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。我们可以在SessionFactoryBuilder.build时指定enviroment,如果没有指定,则会加载默认环境
Default下标注了默认环境
·transactionManger:事务管理器
在 MyBatis 中有两种类型的事务管理器
①JDBC
②MANAGED – 这个配置几乎没做什么。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。
·datasource:数据源
dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。
功能就是连接数据库:db3p,c3p0,druid,HikariCP(springboot内置连接池)
有三种内建的数据源类型:也就是TYPE=”UNPOOLED(无连接池)|POOLED(有连接池)|JNDI(正常连接)”
·池:用完可回收
UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。
POOLED-这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。
(UNPOOLED/POOLED只做了解,我们只需要写默认的就行,即type=POOLED)
·学会使用配置多套环境
·Mybatis默认的事务管理器就是JDBC,连接池:POOLED
5.3属性(Properties)
·我们可以通过ProPerties属性来实现引用配置文件
·这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件(.properties)中配置这些属性,也可以在 properties 元素的子元素中设置。
①在 properties 元素的子元素中设置。
这样的方式导致xml文件过长且不便于维护
所以我们一般通过db.properties文件去配置property,其逻辑就是键值对应,方便日后维护和修改。
②db.properties文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=your username
password=your password
·在核心配置文件中引入db.properties中已经定义好的属性参数
在mybatis中通过properties标签的属性resource引入properties文件
·接着,在environment标签下的property标签中的属性(driver,url…)改为
相应的EL形式
测试执行后发现ok,说明这样的形式是有效的
并且的确更为高效,所以之后我们都会通过引入外部配置文件的形式去引入定义好的属性参数。
注意点:如果在配置文件和properties子标签有同一字段,系统优先使用配置文件的属性参数,因为系统会先读取子标签属性参数,随后再读配置文件的属性参数并且覆盖掉原来子标签的属性参数。
5.4类型别名(typeAliases)
·类型别名可为 Java 类型设置一个缩写名字。
·它仅用于 XML 配置,意在降低冗余的全限定类名书写。
①通过标签下的标签直接对类取别名
<typeAliases>
<typeAlias
type="com.mbw.pojo.User"alias="User"></typeAlias>
</typeAliases>
而这样配置,我们可以在任何使用com.mbw.pojo.User的地方使用User
例如:
<select id="getUserList" resultType="User">
select * from mybatis.user;
</select>
ResultType本来应该是com.mbw.pojo.User,现在可以直接替换为User
②指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean
扫描实体类的包,它的默认别名就为这个类的类名首字母小写,当然执行时别名大写也可以执行,系统推荐使用首字母小写执行
<typeAliases>
<package name="com.mbw.pojo"/>
</typeAliases>
·在实体类比较少推荐使用第一种方式
·如果实体类非常多,推荐使用第二种
·区别:第一种可以DIY别名,第二种则不行,但是也不一定
若第二种方式有注解,则别名为其注解值,比如:
我将这个pojo类取一个Alias注解,注解值为hello
再次执行以前的别名就会执行失败
需要改为与注解值一致
改成注解值再次测试,发现执行成功
③默认别名
·常见的 Java 类型内建的类型别名。它们都是不区分大小写的
会发现基本数据类型的别名就是在别名基础前加_
而不加_的就是包装类型,即原包装类型的小写
5.5设置(settings)
①logImpl:指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
如果设置了,只有这些固定值,这些固定值的功能就是日志的实现(打印日志)
②cacheEnabled:全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。
③lazyLoadingEnabled:懒加载,延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。
这些先暂时只做了解,我们以后会讲到。
5.6其他配置
都不用去过多了解
但在这里提一下piugins
有几款plugins需要了解一下
①mybatis-generator-core:一款自动生产mybatis代码的插件,可以根据你的数据库名字产生mybatis代码完成相应功能
②mybatis-plus:一款第三方插件,与mybatis框架是互补的关系,可以提高mybatis框架的运行效率
③通用Mapper
5.7映射器(mappers)
MapperRegistry:注册绑定我们的Mapper文件:
①使用相对于类路径的资源引用,即引用mapper.xml的绝对路径去注册绑定xml文件【推荐使用】
注意点:一定要在pom文件下去配置resource使得maven能够去获取xml文件
<mappers>
<mapper resource="com/mbw/dao/UserMapper.xml"/>
</mappers>
②使用映射器接口实现类的完全限定类名
注意点:
·接口和他的Mapper配置文件必须同名
·接口和它的Maper配置文件必须在同一个包下
③将包内的映射器接口实现全部注册为映射器
<mappers>
<package name="com.mbw.dao"/>
</mappers>
注意点:
·接口和他的Mapper配置文件必须同名
·接口和它的Maper配置文件必须在同一个包下
六、生命周期和作用域
生命周期和作用域是至关重要的,因为错误的使用会导致非常严重的并发问题
①sqlsessionFactoryBuilder:
·一旦创建了 SqlSessionFactory,就不再需要它了。
·局部变量
②sqlSeesionFactory
·说白了可以想象为数据库连接池
·一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。多次重建是会浪费资源的,一旦高并发环境下,系统极容易宕机
· SqlSessionFactory 的最佳作用域是应用作用域。
·最简单的就是使用单例模式或者静态单例模式。保证全局只有一份这个变量
③sqlSession
·连接到连接池的一个请求
·SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
·用完之后记得关闭,否则资源被占用
这里面的每一个Mapper就代表一个具体的业务!
七、解决属性名和字段名不一致的问题
7.1问题
数据库中的字段
新建一个项目,拷贝之前的,测试实体类和字段不一致的情况
现在将Pojo类原来的pwd改成password,与数据库字段不一致
运行会发现出现问题
原因:
select * from user where id = #{id};
类型处理器:
Select id,name,pwd from user where id=#{id}
系统会去自动找pwd,但是找不到,故只能将返回null
解决方法1:起别名
Select id,name,pwd as password from user where id=#{id};
7.2resultMap
结果集映射
<select id="getUserById" resultType="user" resultMap="UserMap">
select * from user where id=#{id};
</select>
在select标签加一个属性resultMap,然后随意取名
之后加一个resultMap标签
标签内属性id对应刚才select标签中resultMap名字,type对应你要映射的实体类
接着在resultMap标签下加入result标签
在这个标签下加入两个属性,一个是column,一个是property
column对应数据库中的字段,property对应实体类的属性(只能控制基本类型,复杂类型,我们需要单独处理)
<resultMap id="UserMap" type="User">
<result column="pwd" property="password"></result>
</resultMap>
这样映射后测试发现就可以找到
·resultMap 元素是 MyBatis 中最重要最强大的元素。
·ResultMap 的设计思想是,对简单的语句做到零配置,(就是字段和pojo属性一致)对于复杂一点的语句,只需要描述语句之间的关系就行了(哪里不一样映射哪里)。
·ResultMap 的优秀之处——你完全可以不用显式地配置它们。
·如果这个世界总是这么简单就好了。
八、日志
8.1日志工厂
如果一个数据库的操作出现了异常,我们需要排错,日志就是最好的助手
曾经:sout,debug
现在:日志工厂
·logImpl:指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
·SLF4J
·LOG4J 【掌握】
·LOG4J2
·JDK_LOGGING
·COMMONS_LOGGING
·STDOUT_LOGGING 【掌握】
·NO_LOGGING
在Mybatis中具体使用哪一个日志实现,在设置settings标签中设定!
注意点:在设置标签中,name属性和value属性值必须符合规范,必须和官网一致,哪怕大小写,多一个少一个空格都不行
8.2标准日志输出(STDOUT_LOGGING)
在核心配置文件中加入settings标签,name=logImpl,value=STDOUT_LOGGING
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
输出的一堆东西均为日志,我们注意以下内容即可
这个的好处就是假如我的sql语句出错,日志可以很好的帮你定位到错误的地方
日志在这里就停了,说明问题出在sql语句上
8.3 LOG4J
什么是LOG4J
·Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件
·我们也可以控制每一条日志的输出格式
·通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
·这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
①导入依赖
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
②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][%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的使用
直接测试刚才的查询
⑤简单使用
·在要使用log4j的类中导包
import org.apache.log4j.Logger;
·日志对象,参数为当前类的class
static Logger logger = Logger.getLogger(UserMapperTest.class);
·日志级别
logger.info("info:进入了testLog4j");
logger.debug("debug:进入了test4j");
logger.error("error:进入了test4j");
九、分页
思考:为什么分页
·减少数据的处理量
9.1使用Limit分页格式语法:
SELECT * from user limit startIndex,pageSize;
SELECT * from user limit 3;
第一个参数指定第一个返回记录行的偏移量,第二个参数指定返回记录行的最大数目。初始记录行的偏移量是 0
简单来说第一个参数就是从哪开始,第二个参数就是一页返回最大的行数,初始下标从0开始
比方limit 1,2就是从第2行开始,返回2-3行数据
当只有1个参数时,就是从第一行开始,返回指定的最大行数为止,比方limit 3
就是从第一行开始,返回1-3行数据
9.2使用Mybatis实现分页
·核心:sql
①接口
List<User> getUserByLimit(Map<String,Integer> map);
②Mapper.xml
<select id="getUserByLimit" parameterType="map" resultMap="UserMap" resultType="User">
select * from user limit #{startIndex},#{pageSize};
</select>
③测试
@Test
public void TestLimit(){
SqlSession sqlSession=MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String,Integer> map=new HashMap<String, Integer>();
map.put("startIndex",1);
map.put("pageSize",3);
List<User> userList = mapper.getUserByLimit(map);
for (User user : userList) {
System.out.println(user);
}
结果如下:
9.3RowBounds分页(理解为主)
·开发不推荐,理解为主
·它的关键之处在于面向对象
·不再使用SQL实现分页
①接口
List<User> getUserByRowBounds();
②mapper.xml
<select id="getUserByRowBounds" resultMap="UserMap" resultType="User">
select * from user ;
</select>
③Test
@Test
public void TestRowBounds(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
//RowBounds实现
RowBounds rowBounds = new RowBounds(1, 3);
//通过java代码实现分页
List<User> userList = sqlSession.selectList("com.mbw.dao.UserMapper.getUserByRowBounds",null,rowBounds);
for (User user : userList) {
System.out.println(user);
}
}
9.4分页插件(PageHelper)
·了解即可
①导入依赖
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
②配置拦截器插件
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 使用下面的方式配置参数,后面会有所有的参数介绍 -->
<property name="param1" value="value1"/>
</plugin>
</plugins>
③具体使用看文档,底层仍然是Limit
https://pagehelper.github.io/docs/howtouse/
十、注解
10.1面向接口编程
·大家之前都学过面向对象编程,也学习过接口,但在真正开发的时候,很多时候我们会选择面向接口编程。
·根本原因:解耦,可拓展,提高复用,分层开发中,上层不用管具体的实现,大家都遵守共同的标准,使得开发变得容易、规范性更好。
在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。
①关于接口的理解。
·接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。
·接口的本身反映了系统设计人员对系统的抽象理解。
·接口应有两类:第一类是对一个个体的抽象,它可对应为一个抽象体(抽象类);
·第二类是对一个个体某一方面的抽象,即形成一个抽象面(接口);
·一个体有可能有多个抽象面。抽象体与抽象面是有区别的。
②三个面向区别
面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法
面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现
接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题,更多的体现就是对系统整体的架构
10.2使用注解开发
对于像 映射器类来说,还有另一种方法来完成语句映射。 它们映射的语句可以不用 XML 来配置,而可以使用 Java 注解来配置。
①在接口建立一个方法,并为其添加select标签
@Select("select * from user")
List<User> getUserList();
②在mybatis-config核心配置文件中绑定接口,而非mapper.xml文件
<mappers>
<mapper class="com.mbw.dao.UserMapper"/>
</mappers>
③Test
@Test
public void TestSelect(){
SqlSession sqlSession= MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
注意点:由于我们的pojo类password属性名与数据库字段名不一致,password会为null
本来我们可以通过xml中resultMap解决,但在注解中就力不从心了。
·使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
·注解开发的底层主要应用就是反射机制实现
底层:动态代理
10.3Mybatis执行流程剖析
10.4注解完成crud
我们可以在工具类创建的时候实现自动提交事务
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}
在括号内参数改为true,表示自动提交事务
①按照指定id查询用户
@Select("select * from user where id=#{id}")
User getUserById(@Param("id") int id);
在这里我们会需要在方法中加入参数
·方法存在多个基本类型参数,所有参数前面必须添加@Param()注解
·且查询语句输入的id以注解的参数名为主,两者必须一致!
就比方上面的sql语句中id=#{id},这里的#{id}必须和注解名一致
不一样就找不到!
·基本类型放入参数或者String类型,需要加上
·引用类型不需要加
·如果只有一个基本类型的话,可以忽略,但推荐加上
测试:
@Test
public void TestGetUserById(){
SqlSession sqlSession=MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User userById = mapper.getUserById(1);
System.out.println(userById);
}
②添加用户
@Insert("insert into user (id,name,pwd) values(#{id},#{name},#{password})" )
int addUser(User user);
注意这里#{}必须得和你要传入的参数属性名一致
在这里就是User的属性名,比方我user属性名为password,在这里就是password
测试:
@Test
public void TestAddUser(){
SqlSession sqlSession=MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int res = mapper.addUser(new User(4, "余老", "123987"));
if(res>0){
System.out.println("添加成功");
}
}
③修改用户
@Update("update user set id=#{id},pwd=#{password} where name=#{name}")
int UpdateUser(User user);
测试:
@Test
public void TestUpdateUser(){
SqlSession sqlSession=MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int res = mapper.UpdateUser(new User(7, "孟博文", "123987"));
if(res>0){
System.out.println("修改成功");
}
sqlSession.close();
}
④删除用户
@Delete("delete from user where id =#{id}")
int DeleteUser(@Param("id") int id);
测试:
@Test
public void TestDeleteUser(){
SqlSession sqlSession=MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int res = mapper.DeleteUser(7);
if(res>0){
System.out.println("删除成功");
}
sqlSession.close();
}
·#{},${}区别
a、#{}是预编译处理,¥{}是字符串替换。
b、Mybatis 在处理#{}时,会将 sql 中的#{}替换为?号,调用 PreparedStatement 的 set 方法来赋值;
c、Mybatis 在处理¥{}时,就是把¥{}替换成变量的值。
d、使用#{}可以有效的防止 SQL 注入,提高系统安全性。,而¥{}不可以防止Sql注入
所以,能用#{}就用#{}
十一、Lombok
Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.
Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.
·java library:java类库
·plugs插件
·build tools:自动建造工具
·with one annotation your class只需在你类中添加注解就可生成相关代码
使用步骤:
①在IDEA中安装Lombok插件
②导入Lombok依赖(导入jar包)
③在实体类加入你需要的注解即可
@Getter and @Setter
@FieldNameConstants
@ToString
@EqualsAndHashCode
@AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Data
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows
·说明:
@Data :自动生成无参构造,getter,setter代码,toString代码,hashcode,equals
@getter:自动生成get方法
@setter:自动生成set方法
@AllArgsConstructor:构造全参构造器
@NoArgsConstructor:构造全参构造器
@ToString:自动生成tostring方法
@EqualsAndHashCode:自动生成equal方法和Hashcode方法
注意点:
公司用你再用
公司不用就尽量靠自己去写
十二、多对一处理
·就好比多个学生对应一个老师。
·对于学生而言, 关联,多个学生关联一个老师(多对一)
·对于老师而言 集合,一个老师,有很多的学生(一对多)
Sql:
12.1复杂查询环境搭建
①创建一个新工程mybatis_05
②创建数据库
Student.sql:
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`id` int(10) NOT NULL,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`tid` int(10) NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `fktid`(`tid`) USING BTREE,
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `student` VALUES (1, '小明', 1);
INSERT INTO `student` VALUES (2, '小红', 1);
INSERT INTO `student` VALUES (3, '小李', 1);
INSERT INTO `student` VALUES (4, '小孟', 1);
INSERT INTO `student` VALUES (5, '小王', 1);
SET FOREIGN_KEY_CHECKS = 1;
Teacher.sql:
DROP TABLE IF EXISTS `teacher`;
CREATE TABLE `teacher` (
`id` int(10) NOT NULL,
`name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `teacher` VALUES (1, '黄老师');
SET FOREIGN_KEY_CHECKS = 1;
③导入lombok
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
④新建实体类Teacher,Student
Student
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
private int id;
private String name;
//学生需要关联一个老师
private Teacher teacher;
}
Teacher
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
int id;
String name;
}
⑤建立Mapper接口(StudentMapper,TeacherMapper)
⑥建立Mapper.XML文件
Student
<?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核心配置文件-->
<mapper namespace="com.mbw.dao.StudentMapper">
</mapper>
Teacher
<?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核心配置文件-->
<mapper namespace="com.mbw.dao.TeacherMapper">
</mapper>
⑦在核心配置文件中绑定注册我们的Mapper接口或者文件【方式很多,随心选】
<mappers>
<mapper class="com.mbw.dao.TeacherMapper"/>
<mapper class="com.mbw.dao.StudentMapper"/>
</mappers>
⑧测试查询是否成功
@Test
public void TestTeacher(){
SqlSession sqlSession= MybatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacher(1);
System.out.println(teacher);
}
12.2多对一的处理
·需求:查询一个学生的所有信息,以及他们对应的老师
·sql语句:
select s.id,s.name,t.name from student s,teacher t where s.tid=t.id;
那么怎么在mapper.xml文件中写?
思路:
①查询所有学生信息
②根据查询出来的学生的tid,寻找对应的老师
即select * from student;
Select * from teacher where id=#{id},
把这两句sql(两张表)关联起来
方法:通过resultMap
我们之前学过:
在resultMap标签下加入result标签
在这个标签下加入两个属性,一个是column,一个是property
column对应数据库中的字段,property对应实体类的属性
但result标签中property只能控制基本类型
复杂类型,我们需要单独处理
①对象:association标签
②集合:collection标签
·按照查询嵌套处理
①在select标签id=getStudent中加入resultMap:
因为我们现在需要的是teacher,是一个复杂类
那么我们要加入association标签,
其中property仍对应student类中的teacher属性,cloumn对应数据库tid字段,但我们要的不是tid,要的是老师,所以这里我们需要来一个嵌套查询,所以这里javaType=teacher意在返回的是teacher,select=getTeacher(就是下面select标签中id=getTeacher,即类似子查询)
<select id="getStudent" resultMap="studentTeacher">
select * from student;
</select>
<resultMap id="studentTeacher" type="Student">
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="Teacher">
select * from teacher where id=#{tid}
</select>
②测试
@Test
public void TestStudent(){
StudentMapper mapper = MybatisUtils.getSqlSession().getMapper(StudentMapper.class);
List<Student> studentList = mapper.getStudent();
for (Student student : studentList) {
System.out.println(student);
}
}
·按照结果嵌套处理
①在这里我们直接通过结果将老师嵌套进来
仍然在接口增加一个方法getStudent2,
在Student.mapper中加入select标签,与此同时增加一个结果集映射
在select标签中加入sql语句,这个sql是可以直接查出我们想要的结果的
并将s.id取别名sid,s.name=sname,t.id=tid.
在resultMap标签中加入结果映射,因为我最终返回的对象实质上还是学生
所以type=student
首先是result标签,id和取的sid别名对应,name和sname对应
再就是teacher了,复杂类仍然是association标签
property为学生类中的teacher,javaType则为要返回的teacher类
在这里我们就不再子查询,而是直接冲着结果来,想要tname
那么我们就在association标签中嵌套一个result标签
其中property为teacher类的name属性,column对应tname
这样就能将两表连接起来,正常执行我们的sql语句
<select id="getStudent2" 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">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
②测试
@Test
public void TestStudent2(){
StudentMapper mapper = MybatisUtils.getSqlSession().getMapper(StudentMapper.class);
List<Student> studentList = mapper.getStudent2();
for (Student student : studentList) {
System.out.println(student);
}
}
·回顾Mysql多对一查询方式
①子查询------对应刚才的查询嵌套处理
②联表查询------对应刚才的结果查询嵌套处理
十三、一对多处理
比如:一个老师拥有多个学生!
对于老师而言,就是一对多的关系
环境搭建,和刚才一样
实体类
package com.mbw.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
private int id;
private String name;
private int tid;
}
package com.mbw.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
int id;
String name;
//一个老师拥有多个学生
private List<Student> students;
}
需求仍然是查询老师和学生的所有信息
主要对于老师类,因为现在我们做的是一对多,所以我们应该给老师实体类加入一个学生类集合
2、sql语句
Select s.id sid,s.name sname,t.name tname
From Student s,teacher t
Where s.tid=t.id;
3、mapper处理
①结果嵌套查询
·在TeacherMapper中加入GetTeacher()方法,根据id查询指定的老师及其学生信息
Teacher GetTeacher(@Param(“tid”) int id);
·在TeacherMapper.xml中加入select标签,我们在这里通过结果嵌套查询
并设计一个结果集映射,写入刚刚实现的sql语句
在结果集映射,因为我们最终返回的是Teacher类型,所以type为Teacher
添加result标签,将Teacher两个属性映射
接着我们要获取学生信息,由于在Teacher类中我们的学生是放在一个集合中
复杂类型,我们需要单独处理
①对象:association标签
②集合:collection标签
那么很自然我们选择collection标签,property指定我们Teacher类中的集合名students,由于students是集合,用JavaType是不行的,因为JavaType获取的list,二我们想要的是list遍历后的student信息
所以在这里我们得使用ofType去获取集合中泛型的信息
JavaType是指定属性类型,ofType指定泛型的类型
·对student里面的信息作结果映射,分别对student的id,name,tid属性映射
<select id="GetTeacher" resultMap="StudentTeacher">
select s.id sid,s.name sname,t.name tname,t.id tid
from Student s,teacher t
where s.tid=t.id and tid=#{tid};
</select>
<resultMap id="StudentTeacher" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<collection property="students" ofType="Student">
<result property="id" column="sid"></result>
<result property="name" column="sname"></result>
<result property="tid" column="tid"></result>
</collection>
</resultMap>
4、Test
@Test
public void getTeacher1(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = mapper.GetTeacher(1);
System.out.println(teacher);
sqlSession.close();
}
查询成功!
②查询嵌套
StudentMapper和TeacherMapper分别添加一个方法,一个是根据id查询老师,一个是根据传进的tid查询学生
Mapper.xml
跟多对一一样
两个select标签分别根据tid用来查询学生和老师信息,注意这里的id,#{tid}代表你传进来的tid,而id代表老师表的id,tid代表学生表的属性tid
然后在select标签里id=GetTeacher的加入resultMap
resultMap中加入要映射的学生集合collection标签
property代表集合名,ofType代表泛型信息,column代表你这个对应teacher表中的id,因为我们是要根据老师id找学生,select代表要嵌套的查询语句,即select中id=GetStudent的标签
<select id="GetTeacher2" resultMap="TeacherStudent2">
select * from teacher where id=#{tid};
</select>
<resultMap id="TeacherStudent2" type="Teacher">
<collection property="students" column="id" ofType="Student"
select="GetStudent"></collection>
</resultMap>
<select id="GetStudent" resultType="Student">
select * from student where tid=#{tid};
</select>
小结:
①Association [多对一]
②Collection [一对多]
③javatype| ofType
javaType:用来指定实体类中的类型信息
OfType:用来指定映射到list或者集合中的pojo类型,即泛型中的约束类型
注意点:
①保证sql的可读性,尽量保证通俗易懂
②注意一对多和多对一属性和字段的对应问题
③如果发生错误且难以排错,推荐使用log4j日志
④避免慢sql:
尽量多去积累,不断比较,学习最优效率sql
今后一定要积累:
1、Mysql引擎
2、InnoDB底层原理
3、索引
4、索引优化
十四、动态SQL
14.1何为动态sql
动态sql就是指根据不同的条件生成不同的sql语句
使用jdbc时,根据不同条件拼接 SQL 语句有多痛苦
但是,利用动态 SQL,可以彻底摆脱这种痛苦。
动态sql和JSTL 或任何基于类 XML 语言的文本处理器
在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
if
choose (when, otherwise)
trim (where, set)
Foreach
14.2搭建环境
①创建一个新工程Mybatis_07
并把之前的Utils工具类,db.properties,log4j.properties,mybatis-config.xml文件放进相应位置
②创建blog实体类
package com.mbw.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class blog {
private String id;
private String title;
private String author;
private Date createTime;
private int views;
}
③创建blogMapper和blogMapper.xml(记得在核心配置文件绑定mapper)
④创建blog数据库表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `blog`;
CREATE TABLE `blog` (
`id` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`title` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`author` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`create_time` datetime(0) NOT NULL,
`views` int(30) NOT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
⑤创建IDUtils工具类
//在企业中,id并不是1,2,3。。。这样子的顺序
//为了保证id是唯一,一般采用随机的UUID
package com.mbw.dao.util;
import org.junit.Test;
import java.util.UUID;
@SuppressWarnings("all")//抑制警告
public class IDUtils {
public static String getId(){
return UUID.randomUUID().toString().replaceAll("-","");
}
@Test
public void test(){
System.out.println(IDUtils.getId());
System.out.println(IDUtils.getId());
System.out.println(IDUtils.getId());
}
}
注意点:
这里pojo类中的createTime与数据表中的create_time属性名和字段名不一致
你当然可以通过resultMap解决
但在这里还有一个办法,其实这两个名字并不是因为单纯的名字不同,而是因为数据库独有的命名规则,必须加下划线,否则字母就全部大写。所以他们二者不仅不是完全不一致,甚至还可以相互转换
所以我们可以在核心配置文件中setting标签加入name=mapUnderscoreToCamelCase这个属性,可以让它开启自动驼峰命名规则,即从经典数据库列名A_COLUMN到经典Java属性名aColumn的类似映射
<setting name="mapUnderscoreToCamelCase" value="true"/>
⑥插入数据
·在blogMapper中加入addBlog方法
public interface BlogMapper {
int addBook(Map<String,Object> map);
}
·在blogMapper.xml中添加insert标签
<insert id="addBook" parameterType="map">
insert into blog (id,title,author,create_time,views)
value(#{id},#{title},#{author},#{createTime},#{views});
</insert>
·测试
public class TestMybatis {
@Test
public void TestAddBook(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map<String,Object> map=new HashMap<String, Object>();
map.put("id", IDUtils.getId());
map.put("title","Mybatis如此简单");
map.put("author","mbw");
map.put("createTime",new Date());
map.put("views",9999);
mapper.addBook(map);
map.put("id",IDUtils.getId());
map.put("title","Java如此简单");
mapper.addBook(map);
map.put("id",IDUtils.getId());
map.put("title","Spring如此简单");
mapper.addBook(map);
map.put("id",IDUtils.getId());
map.put("title","SpringMVC如此简单");
mapper.addBook(map);
sqlsession.close();
}
由于在工具类创建sqlsession的方法参数已经设置了true,即自动提交事务,所以不用再写sqlseesion.commit;
14.3动态sql之IF语句
IF:使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。
需求:现在我要不在sql语句直接加入where子句的情况下查询
比如:我要根据title和author找到指定的blog
但是我的sql语句中只写select * from blog
这就需要用到动态sql中的IF语句
①在接口中加入方法
②在Mapper.xml中加入select标签
Where后面必须得接条件,不然会报错,为了能够拼接我们的动态sql语句
我们加入1=1即true可以查出所有内容来“应付”。
接着加入if标签,后面跟着test属性,写上条件
比如这里的意思就是如果titlte不为空,则我根据你传入的title查询博客
后面的author也是如此。and起到拼接作用
记得由于我们最终返回类型是一个复杂类型,所以要写resultType
如果是基本类型可以不用写
<select id="SelectBlog" parameterType="map" resultType="blog"
>
select * from blog where 1=1
<if test="title !=null">
and title=#{title}
</if>
<if test="author!=null">
and author=#{author}
</if>
</select>
③测试:
@Test
public void TestSelect(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map<String,Object> map=new HashMap<String, Object>();
map.put("author","mbw");
List<Blog> blogs = mapper.SelectBlog(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
14.4动态sql之choose,when,otherwise语句
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
比如:我们想要通过各种各样的条件去查询我们的blog,但只允许map传入一个参数,根据这个参数找到我们需要的blog
①接口加入方法
List<Blog> SelectBlogByChoose(Map<String,Object> map);
②在blogMapper.xml加入select标签
<select id="SelectBlogByChoose" parameterType="map" resultType="blog">
select * from blog where 1=1
<choose>
<when test="title !=null">
and title=#{title}
</when>
<when test="author!=null">
and author=#{author}
</when>
<when test="views>=5000">
and views=#{views}
</when>
<otherwise>
and id=#{id}
</otherwise>
</choose>
</select>
这样就实现了只传一个参数实现不同条件下的查询功能
这三个标签效果非常类似于JSTL的标签
③测试:
@Test
public void TestSelect2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map<String,Object> map=new HashMap<String, Object>();
map.put("id","379427b8817c4fb6a9f78509944d6b95");
List<Blog> blogs = mapper.SelectBlogByChoose(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
当然我这里调试的是otherwise语句的条件,你也可以尝试去通过其他条件,都没问题,因为笔者已经尝试过了。
14.5trim,where, set标签
①where标签
我们刚才做拼接时,为了sql语句正常,我们会加上where 1=1,不然就会发生
Where and这样的尴尬情况,甚至where后头啥也没有。
但是where 1=1 我们实际开发用这个得被上级“打死”,所以mybatis很友好
给我们添加了where标签
它会智能的添加或去掉and
拿刚才的if标签测试
·在xml文件中select标签的测试if做点修改,将where 1=1改为插入where标签
<select id="SelectBlogByIf" parameterType="map" resultType="blog">
select * from blog
<where>
<if test="title !=null">
and title=#{title}
</if>
<if test="author!=null">
and author=#{author}
</if>
</where>
</select>
测试:有没有发现,我没有把and去掉,本来sql拼接会出现where and 的诡异情况
但是通过日志我们发现where标签很智能的把and去掉了
并且能够成功查询出来
@Test
public void TestSelect(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map<String,Object> map=new HashMap<String, Object>();
map.put("author","mbw");
List<Blog> blogs = mapper.SelectBlogByIf(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
注意点:
当然你什么也不插入,即if条件我均不满足时,本来会出现where后头无条件的sql语句错误,但是我们再次测试,发现仍然能成功。我们来看看日志中where标签将sql语句改造成了什么。
@Test
public void TestSelect(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map<String,Object> map=new HashMap<String, Object>();
List<Blog> blogs = mapper.SelectBlogByIf(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
我们发现,where被去掉了,所以我们可以很轻松的证明官方对where标签的解释:
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
②set标签
·一般set标签咱们用在更新语句
·用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。
我们一般set字段为一个新值时,当需要修改的字段过多,就需要在字段与字段之间加逗号,而后面where筛选条件时,在where和前一个需要修改的字段连接时又不需要逗号,这样的规则之下很多人容易出错,比方多了逗号,set可以动态地在行首插入 SET 关键字,并会删掉额外的逗号。
现在我们来证实一下:
·在接口中添加UpdateBlog方法
int UpdateBlog(Map<String,Object> map);
·在xml文件中添加update标签,在这里我们加入set标签,测试set,并且我们在author=#{author)后多加一个逗号,看看set标签会不会删去多余的逗号
<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 TestUpdate(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map<String,Object> map=new HashMap<String, Object>();
map.put("id","379427b8817c4fb6a9f78509944d6b95");
map.put("title","JavaWeb如此简单");
map.put("author","hqh");
int i = mapper.UpdateBlog(map);
if(i>0){
System.out.println("修改完成");
}
sqlSession.close();
}
查看日志,我们发现set标签确实将最后的逗号删去,并且更新成功
③trim标签
我们可以通过自定义 trim 元素来定制 where,set 元素的一些功能,比如:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
<trim prefix="SET" suffixOverrides=",">
...
</trim>
其中prefix是前缀,prefixOverride是前缀覆盖的意思,与之相反的suffixOverrides是后缀覆盖的意思
prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides(suffixOverrides) 属性中指定的内容,并且插入 prefix 属性中指定的内容。也就是在这里也很容易的看出来,我们可以用trim标签去自定义地加入切割sql语句,用的更加方便,但是我们一般where,set标签就足够了,trim更多还是用不到的,当然还是需要了解一下。
14.6forEach标签
①动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。
我们有时候会看到比如有10个用户地表,我们只想选择id=1,2,3的用户
就会出现这样的sql语句
Select * from user where1=1 and (id=1 or id=2 or id=3);
而forEach就是去完成这样的一个功能。
foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多牛批!虽然用的不多
②我们看一下forEach的一个用例
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
我们在拿刚才写的sql语句看看
Select * from user where1=1 and (id=1 or id=2 or id=3)
拿foreach试着模仿后面的(id=1 or id=2 or id=3)
item表示我们要输出地元素是id,collections表示id组成的集合,集合我们可以通过map传递,separator表示分隔符为or,item表示遍历出来地元素名为id
<foreach item="id" collection="ids"
open="(" separator="or" close=")">
#{id}
</foreach>
③使用
现在我们真正地来用ForEach
·在接口中添加一个方法
//查询第1-2-3号的博客(为了测试方便,我们在数据库先将id改成1,2,3
// 你自行添加3行也是行的)
List<Blog> SelectBlogByForEach(Map<String,Object> map);
·接着我们在blogMapper.xml文件中添加select标签,where标签
到目前我们就实现了刚才sql语句Select * from user where 1=1这个部分
下面我们要实现的就是and( id=1 or id=2 or id=3)
这时我们就要通过forEach标签实现这个功能
首先forEach标签一开始要你输入集合名,我们之后通过map传入这个集合,取名为ids, 接着遍历出的元素名我们取名id,当然可以随意取,open因为我们的sql有and,不妨先加上,反正where会为我们去掉如果不需要的话,那么open就是and(,end就是),那么中间的分隔符用or
那么foreach标签中间的内容就是我们最终需要输出的内容
那么id=#{id},表示id=集合遍历出来的元素,
是不是就形成了
And(id=#{id} or id=#{id} or …)
<select id="SelectBlogByForEach" parameterType="map" resultType="blog">
select * from blog
<where>
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id=#{id}
</foreach>
</where>
</select>
·测试
我们加入一个list集合,并在集合中输入内容,内容当然就是我们想要的id值了
并把它传给map提交。执行后通过日志我们就可以看出来foreach拼接成功
select * from blog WHERE ( id=? or id=? )
?代表我们传入的集合遍历出来的值
果然后头就跟着1(Integer), 2(Integer)
那么就变成了select * from blog WHERE ( id=1 or id=2 ),完成!
@Test
public void TestSelect3(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map<String,Object> map=new HashMap<String, Object>();
ArrayList<Integer> ids=new ArrayList<Integer>();
ids.add(1);
ids.add(2);
map.put("ids",ids);
List<Blog> blogs = mapper.SelectBlogByForEach(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
14.7 SQL片段
有的时候,我么可能会将一些功能的部分抽取出来,方便复用。
比方我们之前在测试set,where标签都重复用到了一段if的sql语句,这时我们就可以把这段sql语句提取出来,方便我们复用。
<if test="title !=null">
and title=#{title}
</if>
<if test="author!=null">
and author=#{author}
</if>
以上面sql语句为例子,我们现在将它提取为sql片段
我们在blogMapper.xml文件最前面添加一个sql标签
给它id取个名,为的是调用这个sql片段
标签间内容就把我们想要复用的这段sql代码放进去就行了
<sql id="if-title-author">
<if test="title !=null">
and title=#{title}
</if>
<if test="author!=null">
and author=#{author}
</if>
</sql>
那么怎么调用呢。我们通过include标签去调用
include标签有一个refid属性,这个属性很明显与咱们sql标签的id对应,所以,我们把装有想要复用的sql标签的id填入refid即可达到复用sql片段的效果
<select id="SelectBlogByIf" parameterType="map" resultType="blog">
select * from blog
<where>
<include refid="if-title-author"></include>
</where>
</select>
注意点:
·最好基于单表来定义SQL片段
·sql片段内最好不要有where,set标签
总结一下:
·所谓的动态SQL,本质还是SQL语句,只是我们可以在SQL层面,去执行一个逻辑代码,if,choose,when,otherwise等等,而where,set只是保证你的sql语句更加安全等等。
·动态sql就是在拼接SQL语句,我们只需要保证SQL的正确性,按照SQLd的格式,去排列组合就可以了
建议:
·先在Mysql中写出完整的SQL再对应的去修改成为我们的动态SQL实现通用即可!
十五、缓存
15.1缓存简介
查询:连接数据库,耗资源!
如何优化:一次查询的结果,给它暂时存在一个可以直接取到的地方!–>内存:而放在内存中的这些数据就叫做缓存
这样我们再次查询相同数据的时候,直接走缓存,就不用走数据库了
①什么是缓存[Cache]
·存在内存中的临时数据
·将用户经常查询到的数据放在缓存中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题
②为什么使用缓存
·减少和数据库的交互次数,减少系统开销,提高系统效率
③什么样的数据能经常使用缓存
·经常查询并且不经常改变的数据。
15.2Mybatis缓存
·MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。
·Mybatis系统默认定义了两级缓存:一级缓存和二级缓存
。默认情况下,只有一级缓存开启(Sqlsession级别的缓存,也称为本地缓存)
。二级缓存需要手动开启和配置,他是基于namespace级别的缓存
。为了提高扩展性,Mybatis定义了缓存接口Cache,我们可以通过实现Cache接口自定义二级缓存
·为了让缓存能够正常运行,清除策略是必要的,在Mybatis中,我们需要掌握两个清除策略
。LRU – 最近最少使用:移除最长时间不被使用的对象。
。FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
15.3一级缓存
·一级缓存也叫本地缓存:SqlSession,根据它的生命周期,就是从它被建立到关闭的这样一个范围,就是一级缓存的作用范围及生命周期。
。与数据库一次会话期间查询到的数据会放在本地缓存中
。以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库
测试步骤:
①创建新模块Mybatis_08
②老一套,该删的删,该复制的复制。只保留查询功能即可
③注意点:一定要开启日志,便于观察
④测试:
为了观察第二次取重复数据的时候是再次查询数据库还是直接从缓存中拿,我们测试两次通过同一个id查询:
@Test
public void TestSelect(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Blog blog = mapper.SelectBlog(1);
System.out.println(blog);
System.out.println("==============================");
Blog blog2 =mapper.SelectBlog(1);
System.out.println(blog2);
}
运行后观察日志:我们发现同一个sql只执行了一次,却可以输出我们两次的查询结果,说明一级缓存默认存在,且如果需要获取相同的数据,系统会直接从缓存中拿。甚至当我们测试blog和blog2两个对象是否相等既然也返回的是true,那么说明它俩地址都是一个。
·缓存失效的情况
我们现在知道了映射语句文件中的所有 select 语句的结果将会被缓存。那么现在来看看缓存失效是由什么引起。
。查询不同的东西(包括不同的内容,当然也包括不同的mapper)
。 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
比方在刚才的测试中我加入一句update语句,来测试一下缓存是否会被刷新
@Test
public void TestSelect(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map<String,Object> map=new HashMap<String, Object>();
map.put("author","mbw");
map.put("id",1);
Blog blog = mapper.SelectBlog(1);
System.out.println(blog);
System.out.println("==============================");
mapper.UpdateBlog(map);
Blog blog2 =mapper.SelectBlog(1);
System.out.println(blog2);
System.out.println(blog==blog2);
}
运行后查看日志,发现同一个select执行了两次,且blog和blog2地址不再相等,说明缓存被刷新
增删改操作,可能会改变原来的数据,所以必定会刷新缓存
。缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
。缓存不会定时进行刷新(也就是说,没有刷新间隔)。
。手动清理缓存
sqlSession.clearCache();
运行后查看日志发现两者不再相同,且sql执行了两次,说明缓存被刷新
小结:一级缓存是默认开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个区间段,一级缓存相当于一个Map,用的时候取,不用的时候放在那里。所以当一个用户多次取相同东西才有用,但取不同或者是多个用户来取(多个用户相当于多个SqlSession)一级缓存就没多大用处了。
15.4二级缓存
·默认情况下,只启用了一级缓存,它仅仅对一个会话中的数据进行缓存。刚才也了解到一级缓存的局限性(作用域太低),所以诞生了二级缓存
·基于nameSpace级别的缓存,一个名称空间,对应一个二级缓存
·工作机制:
。一个会话查询一条语句,这个数据就会被放在当前会话的一级缓存中
。如果当前会话被关闭了,这个会话对应的一级缓存就没了,但我们的需求是,会话关闭了,一级缓存中的数据会被保存到二级缓存中;
。新的会话查询信息,就可以从二级缓存中获取内容
。不同的mapper查出的数据会放在自己对应的缓存(map)中;
步骤:
①开启全局缓存
在settings设置cacheEnabled:全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存,默认为true.但一般情况下我们做好还是显式的表现出来!
②在当前mapper.xml中使用二级缓存
<cache eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
这里介绍一些概念:
1、清除策略:
可用的清除策略有:(知道红色地就可以)
·LRU – 最近最少使用:移除最长时间不被使用的对象。
·FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
·SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
·WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
默认的清除策略是 LRU。、
2、刷新间隔(flushInterval):可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
3、引用数目(size)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
4、只读(readOnly)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
当然,你也可以不加任何参数,这些属性配置值就是默认值
<cache/>
注意点:我们可以在sql标签内人为的设置是否使用缓存
比如:
<select id="SelectBlog" resultType="Blog" useCache="true">
那么useCache就代表是否使用缓存,true代表使用,false代表不使用
③测试:
创建第二个sqlSession和第二个mapper,这样的好处就是故意通过两个mapper,越过一级缓存地限制,有效的测试二级缓存
现在我们这样测试,由于一级缓存关闭后,数据才会被放到二级缓存,
所以我们就查看如果第一次查完,第二次连接再去查同样的数据sql语句会不会执行两次,以此判断二级缓存有没有开启。
@Test
public void TestSelect(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
SqlSession sqlSession2 = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
BlogMapper mapper2 = sqlSession2.getMapper(BlogMapper.class);
Map<String,Object> map=new HashMap<String, Object>();
map.put("author","mbw");
map.put("id",1);
Blog blog = mapper.SelectBlog(1);
System.out.println(blog);
sqlSession.close();
System.out.println("==============================");
// mapper.UpdateBlog(map);
Blog blog2 =mapper2.SelectBlog(1);
System.out.println(blog2);
System.out.println(blog==blog2);
sqlSession2.close();
}
在不开启二级缓存地情况下,sql语句可以很明显发现执行了两次
在开启二级缓存后再运行:
发现sql语句只执行了一次,说明二级缓存被开启
15.5缓存原理
①缓存顺序:
1、用户查询先查看二级缓存有没有记录
2、二级缓存没有再到一级缓存查看
3、一级缓存都没有再连接数据库,去数据库检索
②流程图:
③测试:为了检测缓存的顺序,我们再刚才查询id=1的用户基础上再查询id=2的用户。
@Test
public void TestSelect(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
SqlSession sqlSession2 = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
BlogMapper mapper2 = sqlSession2.getMapper(BlogMapper.class);
Map<String,Object> map=new HashMap<String, Object>();
map.put("author","mbw");
map.put("id",1);
Blog blog = mapper.SelectBlog(1);
System.out.println(blog);
sqlSession.close();
System.out.println("==============================");
// mapper.UpdateBlog(map);
Blog blog2 =mapper2.SelectBlog(1);
System.out.println(blog2);
System.out.println(blog==blog2);
System.out.println("==============================");
Blog blog3=mapper2.SelectBlog(2);
System.out.println(blog3);
Blog blog4=mapper2.SelectBlog(2);
System.out.println("==============================");
System.out.println(blog4);
sqlSession2.close();
}
我们发现它只执行了两次sql,第二次就是去查询数据库,因为两级缓存均没有,且当我们再次查询id=2的用户 ,我们发现它不再去执行sql,直接到缓存去拿,这其中有一级缓存的blog4,二级缓存的blog2.执行sql语句的blog,blog3
总结:注意缓存的顺序
一般工作中让我们调优缓存时,我们经常需要通过对特定标签决定它是否使用缓存。
所以useCache就凸显出用处了,比方我一个查询语句经常刷新,这个时候让它占用cache太浪费了,把useCache调成false就行了
还有希望增删改不更新缓存,也可加上flushCache,属性值false就不再刷新cache
这些都会用在调优cache上
15.6自定义缓存-ehcache
①ehcache是一种广泛使用的Java分布式缓存,主要面向通用缓存
要在程序中使用ehcache
·先导包(依赖)
<!-- mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
·再mapper引入自定义缓存ehcache
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
·编写ecache.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="java.io.tmpdir/Tmp_EhCache"/>
<!--
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,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
-->
<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"/>
</ehcache>
注意点:我们不用深究这个自定义缓存,只要知道有这个东西,并且会导入,具体用法仍然是要用的时候再用,就比方这样定义后我们就可以测试,跟其他的并没有什么不同,而且缓存我们之后会用redis去实现缓存。
拓展:(了解)
我们当然也可以自己手写cache
创建一个cache类实现cache接口,然后实现接口方法即可,当然这里我们不用深究,只需了解!
更多推荐
所有评论(0)