MyBatis
一、MyBatis基础
什么是MyBatis
- MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。
- MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
- MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
为什么要学习MyBatis
- 简化了JDBC复杂的代码
- 提高了数据库连接的高效性
- SQL与代码分离,降低了SQL与代码的耦合性,降低维护成本
前戏准备
- 开发环境
- JDK 1.8
- MySQL 5.7
- Maven 3.6.1
- 开发工具
- IDEA
- SQLyog
- Maven
- 依赖支持
- JDBC
- MySQL
- junit
- mybatis
……
1.1、第一个MyBatis程序
1.1、创建数据库表及数据
创建数据库表
CREATE TABLE user
(
id
INT(20) NOT NULL,
name
VARCHAR(30) NOT NULL,
pwd
VARCHAR(20) NOT NULL,
PRIMARY KEY(id
)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
插入数据
INSERT INTO user
VALUES
(1,’张三’,’147852’),
(2,’李四’,’365241’),
(3,’王五’,’987564’),
(4,’老王’,’258463’),
(5,’八戒’,’349182’);
<br />查询结果<br />![](https://gitee.com/hg14150/blogiamges/raw/master/img/image-20210411144341043.png#crop=0&crop=0&crop=1&crop=1&id=nke2T&originHeight=258&originWidth=419&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
<a name="5e3b473c"></a>
#### 1.2、MyBatis实现连接
<a name="0c2d08e0"></a>
##### 1.2.1、环境依赖
```xml
<!--导入依赖-->
<dependencies>
<!--导入myatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<!--导入mysql依赖-->
<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>
<!--导入日志依赖-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- 导入lombok包 -->
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
</dependencies>
<!--配置过滤配置文件,防止因filter null导致运行失败-->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
1.2.2、JDBC连接类(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>
<!--核心配置文件-->
<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?characterEncoding=utf8&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--Mapper.xml文件需要在核心配置下注册-->
<mappers>
<mapper class="com.iflytek.Dao.UserMapper"/>
</mappers>
</configuration>
1.2.3、设计工具类—工厂模式(MyBatisUtils.java)
package com.iflytek.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;
/**
* 创建工厂qlSessionFactory实体工具类
* 实现获取sqlSession
*/
@SuppressWarnings("ALL")
public class MybatisUilts {
private static SqlSessionFactory sqlSessionFactory;
static {
//1.获取qlSessionFactory对象
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//创建执行SQL对象
public static SqlSession getsqlSession(){
return sqlSessionFactory.openSession();
}
}
1.2.4、实体类及实体类接口的编写
1、实体类(User.java)
package com.iflytek.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
//创建mybatis实体类
@Data
@NoArgsConstructor
@AllArgsConstructor@ToString
public class User {
private int id;
private String name;
private String password;
}
2、实体类接口(UserMapper.java)
package com.iflytek.Dao;
import com.iflytek.pojo.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
/**
* 创建操作实体类的接口
*/
//
public interface UserMapper {
/**
* @return
* 1.以集合的形式查詢
*/
List<User> getUserList();
/**
* @param id
* @return
* 2.根据id查询用户
*/
User getUserId(@Param("id") int id);
/**
* @param user
* @return
* 3.插入用户(插入全部信息)
*/
int insertUser(User user);
/**
* @param user
* @return
* 4.修改用户信息
*/
int updateUser(User user);
/**
* @param id
* @return
* 5.删除用户信息
*/
int delete(int id);
}
1.2.5、SQL语句(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">
<!--接口实现类转为Mapper配置文件-->
<!--namespace= 绑定一个对应的Dao/Mapper-->
<mapper namespace="com.iflytek.Dao.UserMapper">
<!----------------------------------------------------------------------------------------------------->
<!--执行SQL语句-->
<!--id为接口方法 resultType为集合返回全类名-->
<select id="getUserList" resultType="com.iflytek.pojo.User">
select * from user
</select>
<!----------------------------------------------------------------------------------------------------->
<!--根据id查询,parameterType:数据类型 #{__}查询根据-->
<select id="getUserId" resultType="com.iflytek.pojo.User" >
select * from user where id =#{id}
</select>
<!----------------------------------------------------------------------------------------------------->
<!--插入语句-->
<insert id="insertUser" parameterType="com.iflytek.pojo.User">
insert into user values(#{id},#{name},#{password});
</insert>
<!----------------------------------------------------------------------------------------------------->
<!--修改用户信息-->
<update id="updateUser" parameterType="com.iflytek.pojo.User">
update mybatis.user set name=#{name},password=#{password} where id = #{id};
</update>
<!----------------------------------------------------------------------------------------------------->
<!--删除用户信息-->
<delete id="delete" parameterType="com.iflytek.pojo.User">
delete from user where id=#{id};
</delete>
</mapper>
1.2.6、测试
package com.iflytek.Dao;
import com.iflytek.pojo.User;
import com.iflytek.utils.MybatisUilts;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
/**
* 业务要求:
* 1.查询所有用户信息
* 2.根据id查询用户信息
* 3.插入用户信息
* 4.修改用户相信
* 5.删除用户相信
*
* 注意事项:
* 1.Mybatis查询必须提交事务 对象.commit()
* 2.没执行一次都要重新释放资源
* 3.返回值类型一定要与UserMapper.java和UserMapper.xml保持一致
*/
public class UserMapperTest {
//1.获取配置连接对象
SqlSession sqlSession = MybatisUilts.getsqlSession();
///////////////////////////////////////////////////////////////////////////////////////////////////////
//查询所有用户信息
@Test
public void queryTest(){
//2.获取getMapper方法
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//3.执行SQL
List<User> userList = mapper.getUserList();
//4.处理结果集
for (User user : userList) {
System.out.println(user);
}
//5.关闭SqlSession
sqlSession.close();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
//根据id查询用户
@Test
public void getUserId(){
//2.获取Mapp方法
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//3.执行SQL
User userId = mapper.getUserId(4);
//4.处理结果集
System.out.println(userId);
//5.事务提交
sqlSession.commit();
//6.释放资源
sqlSession.close();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
//插入用户
@Test
public void insertUser(){
//2.获取Mapper方法
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//3.获取map方法
mapper.insertUser(new User(2, "张三", "101010"));
//4.提交事务
sqlSession.commit();
//6.释放资源
sqlSession.close();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
//修改用户信息
@Test
public void updataUser(){
//2.获取Mapper方法
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//3.执行SQL
mapper.updateUser(new User(1, "八戒", "001100"));
//4.提交事务
sqlSession.commit();
//5.释放资源
sqlSession.close();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
//删除用户信息
@Test
public void deleteUser(){
//2.获取Mapper
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//3.执行SQL
mapper.delete(2);
//4.提交事务,在业务中必须要提交事务
sqlSession.commit();
//5.释放资源
sqlSession.close();
}
}
1.3、总结
1、 Mapper.xml必须在MyBatis核心配置文件(mybatis-config.xml)中配置
<!--Mapper.xml文件需要在核心配置下注册-->
<mappers>
<mapper class="com.iflytek.Dao.UserMapper"/>
<mapper resouce="com/iflytek/Dao/UserMapper.xml"/>
</mappers>
2、查询过滤问题,必须添加过滤依赖
<!--配置过滤配置文件,防止因filter null导致运行失败-->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
3、SQL语句的.XML
类一定要与实体类接口xxxMapper
<!--SQL语句命名空间绑定实体类接口,只有产生交集才能关联数据库查询-->
<mapper namespace="com.iflytek.Dao.UserMapper">
4、隐射返回类型
<!--结果集返回类型,对应只能返回一个结果,默认情况返回的是实体类接口中方法里的范型或实体类-->
resultType=""
<!--id为接口方法 resultType为集合返回全类名-->
<select id="getUserList" resultType="com.iflytek.pojo.User">
select * from user
</select>
<!--Map类型返回结果集,一般不用-->
resultMap=""
5、在业务操作中,必须要提交事务和释放资源
//4.提交事务,在业务中必须要提交事务
sqlSession.commit();
//5.释放资源
sqlSession.close();
6、在业务中SQL操作只需要填充业务需求
@Test
public void queryTest(){
//1.获取配置连接对象
SqlSession sqlSession = MybatisUilts.getsqlSession();
//2.获取getMapper方法
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//3.执行SQL
-----------------------------------------------------
//4.处理结果集
for (User user : userList) {
System.out.println(user);
}
//5.提交事务,关闭SqlSession
sqlSession.commit();
sqlSession.close();
}
1.2、CRUD实现原理
- CRUD实现大致原理一致,代码重复高
- 除了SQL业务层其他的都是固定的,只要实现SQL业务语句
- 在业务中建议使用Map的形式实现CURD,以下内容一map为例
- map操作的好处是可以不用知道数据库里有什么,而是直接按字段查询,参数类型统一
CRUD每个业务的实现都要提交事务和资源释放
//4.提交事务,在业务中必须要提交事务
sqlSession.commit();
//5.释放资源
sqlSession.close();
//参数类型统一 .xml
parameterType="map"
1、数据库设计
CREATE DATABASE `mybatis`;
USE `mybatis`;
CREATE TABLE `user` (
`id` INT(20) NOT NULL,
`name` VARCHAR(30) NOT NULL,
`pwd` VARCHAR(20) NOT NULL,
`gender` VARCHAR(2) NOT NULL,
`height` INT(5) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `user` VALUES
(1,'张三','147852','男',180),
(2,'李四','365241','女',179),
(3,'王五','987564','男',169),
(4,'老王','258463','女',150),
(5,'八戒','349182','男',172);
2、实体类接口
package com.iflytek.Dao;
import com.iflytek.pojo.User;
import java.util.List;
import java.util.Map;
/**
* 创建Map方法实现类的接口
*/
public interface UserMapper_2 {
//查询全部信息
List<User> getUserlist();
//2.根据id查询对象
User getUserId(Map<String,Object> map);
//3.添加对象信息
void insertUser(Map<String,Object> map);
//4.修改对象
int update(Map<String,Object> map);
//5.删除对象
int delete(Map<String,Object> map);
//6.模糊查询
List<User> getUserlike(String value);
}
3、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= 绑定一个对应的Dao/Mapper-->
<mapper namespace="com.iflytek.Dao.UserMapper_2">
<!--执行SQL语句-->
<!--id为接口方法 resultType为集合返回全类名-->
<!--查询所有对象-->
<select id="getUserlist" resultType="com.iflytek.pojo.User">
select * from user_2
</select>
<!--根据id查询对象-->
<select id="getUserId" parameterType="map" resultType="com.iflytek.pojo.User">
select * from user_2 where id=#{userid};
</select>
<!--添加对象-->
<insert id="insertUser" parameterType="map">
insert into user_2 values(#{id},#{name},#{password},#{gender},#{height});
</insert>
<!--修改对象-->
<update id="update" parameterType="map">
update user_2 set name=#{name},password=#{password},gender=#{gender},height=#{height} where id=#{id};
</update>
<!--删除对象-->
<delete id="delete" parameterType="map">
delete from user_2 where id=#{id};
</delete>
<!--模糊查询-->
<select id="getUserlike" resultType="com.iflytek.pojo.User">
<!-- "%"#{value}"%"防止SQL注入-->
select * from user_2 where name like "%"#{value}"%";
</select>
</mapper>
4、业务实现
package com.iflytek.Dao;
import com.iflytek.pojo.User;
import com.iflytek.utils.MybatisUtils_2;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class UserMapperTest_2 {
//1.获取配置连接对象
SqlSession sqlSession=MybatisUtils_2.getsqlSession();
//2.获取getMapper方法
UserMapper_2 mapper = sqlSession.getMapper(UserMapper_2.class);
//查询所有对象
@Test
public void getUserlist(){
//3.执行sql
List<User> userlist = mapper.getUserlist();
//4.处理结果集
for (User user : userlist) {
System.out.println(user);
}
//5.提交事务
sqlSession.commit();
//6.释放资源
sqlSession.close();
}
//根据id查询对象
@Test
public void getUserId(){
//3.执行sql
Map<String, Object> map = new HashMap<String, Object>();
//获取查询map地址
map.put("userid",1);
//提交amp地址
User userId = mapper.getUserId(map);
//处理结果集
System.out.println(userId);
//4.提交事务
sqlSession.commit();
//5.释放资源
sqlSession.close();
}
//添加对象
@Test
public void insertUser(){
//3.执行SQL
HashMap<String, Object> map = new HashMap<String, Object>();
//写入添加信息
map.put("id",7);
map.put("name","阿珂");
map.put("password","112233");
map.put("gender","女");
map.put("height",160);
//提交写入信息
mapper.insertUser(map);
//4.提交事务
sqlSession.commit();
//释放资源
sqlSession.close();
}
//修改对象信息
@Test
public void update(){
//3.执行SQL,获取HashMap
Map<String, Object> map = new HashMap<String, Object>();
//写入修改信息
map.put("id",1);
map.put("name","一婵");
map.put("password","101010");
map.put("gender","女");
map.put("height",179);
//提交修改信息
mapper.update(map);
//4.提交事务
sqlSession.commit();
//5.释放资源
sqlSession.close();
}
//删除对象
@Test
public void delete(){
//3.执行SQL,获取HashMap
Map<String, Object> map = new HashMap<String, Object>();
//写入删除id信息
map.put("id",6);
//提交信息
mapper.delete(map);
//4.提交事务
sqlSession.commit();
//释放资源
sqlSession.close();
}
//模糊查询
@Test
public void getUserlike(){
UserMapper_2 mapper = sqlSession.getMapper(UserMapper_2.class);
List<User> userlike = mapper.getUserlike("%李%");
for (User user : userlike) {
System.out.println(user);
}
sqlSession.commit();
sqlSession.close();
}
}
5、总结
业务实现的固定搭配 ```java //查询所有对象 @Test public void getUserlist(){
//1.获取配置连接对象
SqlSession sqlSession=MybatisUtils_2.getsqlSession();
//2.获取getMapper方法
UserMapper_2 mapper = sqlSession.getMapper(UserMapper_2.class);
//3.执行sql
Map<String, Object> map = new HashMap<String, Object>();
//处理SQL业务
//4.处理结果集
for (User user : userlist) {
System.out.println(user);
}
//5.提交事务
sqlSession.commit();
//6.释放资源
sqlSession.close();
}
- 多参数情况使用**parameterType="map"**
<a name="e4a20289"></a>
### 1.3、配置优化
- 在MyBatis-config.xml中需要配置的内容有几下几个点,在配置文件中一定要按照这个顺序配置
![](https://gitee.com/hg14150/blogiamges/raw/master/img/image-20210412110503579.png#crop=0&crop=0&crop=1&crop=1&id=twFhF&originHeight=377&originWidth=862&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
<a name="48f04ffe"></a>
##### 1.3.1、properties
- `db.properties`
```properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8
username=root
password=123456
- 动态引用配置
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="123456"/>
</properties>
- 可以通过properties动态引用配置文件属性,主要引用
db.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>
- 在使用外部引用时,优先级外部引用大于内部引用
- 整体效果
```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">
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--设置别名,减少冗余-->
<typeAliases>
<!--第一种方法,获取全类名并取别名-->
<typeAlias type="com.iflytek.pojo.User" alias="User"/>
<!--第一种方法,扫描所需类名的上级全类名-->
<package name="com.iflytek.pojo"/>
<!--实体类较少时用第一种,实体类较多时用第二种-->
</typeAliases>
<!--核心配置文件-->
<!--envrionments可配置多环境-->
<environments default="development">
<!--环境配置一 -->
<environment id="development">
<!--数据库管理器:1.JDBC 2.MANAGED-->
<transactionManager type="JDBC"/>
<!--连接池的实现-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!--Mapper.xml文件需要在核心配置下注册-->
<mappers>
<mapper resource="com/iflytek/Dao/UserMapper_3.xml"/>
<!--通过class和package方法必须保证接口与配置文件同名且在同一个包下面-->
<!--<mapper class="com.iflytek.Dao.UserMapper_2"/>-->
<!--<package name="com.iflytek.Dao.UserMapper_2"/>-->
</mappers>
<a name="b8c14e5d"></a>
##### 1.3.2、Settings
```xml
<!--常用设置-->
<settings>
<!--默认开启配置缓存-->
<setting name="cacheEnabled" value="true"/>
<!--默认开启懒加载,延迟加载,一般选择关闭-->
<setting name="lazyLoadingEnabled" value="false"/>
<!--允许返回多个结果集-->
<setting name="multipleResultSetsEnabled" value="true"/>
<!--默认开启实体类驼峰命名-->
<setting name="mapUnderscoreToCamelCase" value="false"/>
<!--设置日志实现-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
1.3.3、typeAliases
- 类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写
- 按类名起别名 ```xml
- 按包名起别名
xml
- 在包下的类中添加注解,默认情况下别名使用小写
java
@Alias(“User”)
public class User {
…
}
- 第一种方式,可以DIY别名,且适用与实体类少的情况
- 第二种方式,别名即类名,适用与实体类多的情况
<a name="e81e7d66"></a>
##### 1.3.4、environments
- 默认环境,默认开发模式**default=“development”**,可改选项
xml
- 默认使用的环境 ID(比如:default="development")
- 每个 environment 元素定义的环境 ID(比如:id="development")
- 事务管理器的配置(比如:type="JDBC"),还有MANAGED
- 数据源的配置(比如:type="POOLED")默认pooled是因为它能让web快速响应请求
<a name="009a21c6"></a>
##### 1.3.5、mappers
建议使用相对路径和限定全类名
- 使用限定全类名绑定时要注意,实体类接口和SQL业务查询文件必须在同一个包下,且类名一致
xml
<a name="64fbf7d4"></a>
### 1.4、生命周期及作用域
生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。
**SqlSessionFactoryBuilder —> SqlSessionFactory —> SqlSession**
-
<a name="SqlSessionFactoryBuilder"></a>
#### SqlSessionFactoryBuilder
- 这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了
-
<a name="SqlSessionFactory"></a>
#### SqlSessionFactory
- 一旦被创建就应该在应用的运行期间一直存在,不允许丢弃它或重新创建另一个实例,
-
<a name="SqlSession"></a>
#### SqlSession
- 每个线程都应该有它自己的 SqlSession 实,相当于多态实例化的请求,用完需要关闭<br />![](https://gitee.com/hg14150/blogiamges/raw/master/img/image-20210412122452842.png#crop=0&crop=0&crop=1&crop=1&id=FVNGR&originHeight=500&originWidth=873&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
<a name="99c5f8d5"></a>
### 1.5、ResultMap
- 结果集映射解决了数据库字段名与实体类变量名不一致的问题,也仅限适用于字段名与变量名不同的情况
xml
- column是数据库字段名,property是实体类变量名,通过属性指向映射将两者连接
- 查询时候直接引用参数,resultMap=“ ”引用
xml
- reslutMap的优秀在于,映射不相同的字段名跟变量名,一致的不用映射
<a name="b889bc53"></a>
## 二、MyBatis进阶
<a name="9fa7b21d"></a>
### 2.1、日志解析
- Mybatis内置的日志工厂提供日志功能,具体的日志实现有以下几种工具:
- SLF4J
- **Apache Commons Logging**(默认使用)
- Log4j 2
- Log4j
- JDK logging
- 标准日志实现
xml
- 有需求就会有市场,在开发中log4j才是日志王者
- Log4j是Apache的一个开源项目
- 通过使用Log4j,我们可以控制日志信息输送的目的地:控制台,文本,GUI组件....
- 我们也可以控制每一条日志的输出格式;
- 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
- 使用步骤:
1. 导入依赖(**pom.xml**)
xml
2. 编写配置文件(**log4j.properties**)
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/iflytek.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
3. Setting设置日志实现
xml
4. 测试日志生成
java
//注意导包:org.apache.log4j.Logger
static Logger logger = Logger.getLogger(MyTest.class);
@Test
public void getUserlist(){
/**
日志级别:
等级info,消息在粗粒度级别上突出强调应用程序的运行过程
等级为debug,低于debug就不会输出
等级error,发生错误事件,但仍然不影响系统的继续运行
/
logger.info(“info:进入selectUser方法”);
logger.debug(“debug:进入selectUser方法”);
logger.error(“error: 进入selectUser方法”);
//3.执行sql
List<a name="6d693c0d"></a>
### 2.2、分页实现
分页在商场类项目中,使用最为广泛,使用分页有以下好处
- 提高了用户体验
- 降低了数据刷新的频率,减少宕机的风险
<a name="4fc3e724"></a>
##### 1、基于持久层的分页实现
- **Limit实现分页**
- SQL语法
#语法
SELECT FROM table LIMIT stratIndex,pageSize
SELECT FROM table LIMIT 5,10; // 检索记录行 6-15#为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为 -1:
SELECT FROM table LIMIT 95,-1; // 检索记录行 96-last.
#如果只给定一个参数,它表示返回最大的记录行数目:
SELECT FROM table LIMIT 5; //检索前 5 个记录行
#换句话说,LIMIT n 等价于 LIMIT 0,n。
- 使用步骤
1. 修改Mapper.xml文件
xml
2. Mapper.java接口,参数为map
xml
//选择全部用户实现分页
List推断:起始位置 = (当前页面 - 1 ) 页面大小
java
//分页查询 , 两个参数startIndex , pageSize
@Test
public void testSelectUser() {
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
int currentPage = 1; //第几页
int pageSize = 2; //每页显示几个
Map<String,Integer> map = new HashMap<String,Integer>();
map.put("startIndex",(currentPage-1)*pageSize);
map.put("pageSize",pageSize);
List<User> users = mapper.selectUser(map);
for (User user: users){
System.out.println(user);
}
session.close();
}
- RowBounds实现分页
- 使用步骤
1. mapper接口
java
//选择全部用户RowBounds实现分页
List<User> getUserByRowBounds();
2. mapper.xml文件
xml
<select id="getUserByRowBounds" resultMap="UserMap">
select * from user
</select>
3. 测试
java
//rowbunds实现分页查询
@Test
public void getUserByRowBunds(){
UserMapper_4 mapper = sqlSession.getMapper(UserMapper_4.class);
int currentPage = 2; //第几页
int pageSize = 2; //每页显示几个
RowBounds rowBounds = new RowBounds((currentPage-1)*pageSize,pageSize);
//通过session.**方法进行传递rowBounds,[此种方式现在已经不推荐使用了]
List<User> users = sqlSession.selectList("com.iflytek.Dao.UserMapper_4.getUserByRowBounds", null, rowBounds);
for (User user: users){
System.out.println(user);
}
sqlSession.commit();
sqlSession.close();
}
##### 2、总结
- limit分页方式是数据库层实现的分页方式
- rowbunds分页方式是Java层面实现的分页方式
### 2.3、神奇的注解
- 使用步骤
1. 编写实体类接口方法
1. 在方法体上添加SQL注解,以达到Mapper.xml的效果
java
package com.iflytek.Dao;
import com.iflytek.pojo.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
import java.util.Map;
//注解查询
@SuppressWarnings("ALL")
public interface UserMapper_5 {
//注解查询全部对象
@Select("select * from user")
List<User> getUsers();
//注解根据id查询对象
@Select("select * from user where id=#{id}")
User getUserById(@Param("id") int id);
//注解添加对象
@Insert(" insert into user values(#{id},#{name},#{password},#{gender},#{height})")
int insertUser(Map<String,Object> map);
//注解修改对象
@Update("update user set name=#{name},password=#{password},gender=#{gender},height=#{height} where id=#{id}")
int update(Map<String, Object> map);
//注解删除对象
@Delete(" delete from user where id=#{id}")
int delete(Map<String, Object> map);
}
3. 测试
java
//注解添加对象
@Test
public void insertUser(){
//1.获取配置连接对象
SqlSession sqlSession= MybatisUtils_5.getsqlSession();
//2.获取getMapper方法
UserMapper_5 mapper = sqlSession.getMapper(UserMapper_5.class);
//注意导包:org.apache.log4j.Logger
static Logger logger = Logger.getLogger(String.valueOf(UserMapper_5.class));
HashMap<String, Object> map = new HashMap<String, Object>();
//写入添加信息
map.put("id",7);
map.put("name","张飞");
map.put("password","102938");
map.put("gender","男");
map.put("height",190);
//提交写入信息
mapper.insertUser(map);
//4.提交事务
sqlSession.commit();
//释放资源
sqlSession.close();
}
- 总结
- 使用注解可以简化代码,这个过程主要体现在Mapper.xml文件的省略
- 注解开发只适用于简单常见的业务,过于复杂的业务不建议使用
- 因为使用注解,不需要创建Mapper.xml文件,在配置文件中绑定注册只能以限定全类名或包名的形式
xml
<!--绑定接口,每写一个就必须先绑定注册,使用*通配符匹配绑定能到达最大化简略-->
<mappers>
<mapper class="com.iflytek.Dao.UserMapper_5"/>
</mappers>
- 在注解开发中,可以设置事务的自动提交,方便测试
java
//实体类接口,设置为true
public static SqlSession getsqlSession(){
return sqlSessionFactory.openSession(true);
}
- 关于@Param()注解
- 基本类型或String的参数,都需要加上
- 引用类型不需要加上
- 可以同时添加不同的参数注解
### 2.4、复杂查询环境
#### 1、MyBatis执行流程
- MyBatis程序执行路线
- 执行流程
#### 2、多查询处理
- 多数据查询无非就是一对多和多对一,以及关联多表查询
- 以老师和学生为例创建主键外键关联表,但实际开发不建议是由物理外键
#创建教师表
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,'张三');
#创建学生表
create table `student`(
`id` int(10) not null ,
`name` varchar(30) default null ,
`tid` int(10) default null,
primary key (`id`),
key `fktid`(`tid`),
constraint `fktid` foreign key (`tid`) references `teacher`(`id`)
)engine =innoDB default charset =utf8;
#插入数据
insert into `student` values (1,'小红',1);
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);
insert into `student` values (6,'小五',1);
- 物理外键的关联效果##### 1、一对多 - 结果集嵌套跟结果集映射是一个重要的思想 - 一对多的查询通常使用结果集嵌套来实现
xml
<!--查询所有对象-->
<select id="getTeacher_1" resultType="com.iflytek.pojo.Teacher">
select * from teacher t,student s where s.id = t.id
</select>
- 整合集合类型使用collection
标签,java映射类型为 javaType=”ArrayList” ofType=”Student”
- javaType & ofType
- javaType 用来指定实体类中的属性
- ofType 用来指定映射到List或集合中的实体类
##### 2、多对一
- 提出需求——查询所有学生信息以及对应老师的信息
- 需求分析
- 查询学生表所有信息
select * from `student`;
- 查询对应老师的信息
select * from `teacher`;
- 关联查询学生信息对应老师信息的自查询
select * from `student` as s,`teacher` as t where s.id=t.id;
- 业务实现
xml
<!--查询学生信息-->
<select id="getStudent" resultMap="Student_Teacher">
select * from student
</select>
- 在java中,每个表对应不同的实体类,这导致了Mapper.xml文件不同,使用SQL语句达不到关联查询的效果
- 因此需要使用association
标签整合,整合对象为teacher
表,整合列为tid
.java映射类型为 resultType=”Teacher”,select查询的对象为id=”getTeacher”,相当于整合谁类型就归谁
- 结果集嵌套处理
#关联Teacher表查询所有学生记录,联表查询
<select id="getStudentLinkTeacher" resultMap="Student_Teacher_2">
select s.id sid,s.name sname,t.name tname
from student s,teacher t where s.tid = t.id;
</select>
- 从SQL层的联表查询,相对来说难度稍大,但是效率极高,安全性相当
- 最好使用结果嵌做业务查询
#### 3、动态SQL问题
什么是动态SQL:动态SQL指的是根据不同的查询条件 , 生成不同的Sql语句,注解开发难以做到这一点。
> MyBatis 的强大特性之一便是它的动态 SQL。
>> 如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。 >
> 例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。 >
> 利用动态 SQL 这一特性可以彻底摆脱这种痛苦。 >
> 虽然在以前使用动态 SQL 并非一件易事,但正是 MyBatis 提供了可以被用在任意 SQL 映射语句中的强大的动态 SQL 语言得以改进这种情形。 >
> 动态 SQL 元素和 JSTL 或基于类似 XML 的文本处理器相似。 > - if > - choose (when, otherwise) > - trim (where, set) > - foreach > —- —- - 创建数据库
#创建数据库表
create table `blog`(
`id` varchar(50) not null comment '博客id',
`title` varchar(30) default null comment '博客标题',
`author` varchar(50) default null comment '作者',
`createTtime` datetime comment '创建时间',
`views` int(100) not null comment '浏览量',
primary key (`id`)
)engine =innoDB default charset =utf8;
#插入数据
insert into `blog` values ('1','java基础','张三','2020-10-10',100);
insert into `blog` values ('2','mysql基础','李四','2020-10-11',110);
insert into `blog` values ('3','redis基础','王五','2021-04-10',120);
insert into `blog` values ('4','python基础','八戒','2019-10-10',101);
- 创建工程
- 编写工具类(MyBatisUtils_9)
java
package com.iflytek.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对象
*
* 在java中,jvm先加载的是静态代码块
*/
public class MybatisUtils_9 {
//1.创建sqlSessionFactory工厂实体类
private static SqlSessionFactory sqlSessionFactory;
//创建代码块
static{
//获取sqlsession对象
try {
//连接配置文件
String resource = "mybatis-config-9.xml";
//获取扫描
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//实体类接口
public static SqlSession getsqlSession(){
return sqlSessionFactory.openSession();
}
}
- 编写UUID工具类(IDUtils.java)
java
package com.iflytek.utils;
import org.junit.Test;
import java.util.UUID;
@SuppressWarnings("all")
public class IDutils {
//设置随机生成id
public static String getId(){
//取消-
return UUID.randomUUID().toString().replaceAll("-","");
}
@Test
public void t(){
System.out.println(IDutils.getId());
System.out.println(IDutils.getId());
System.out.println(IDutils.getId());
}
}
- 编写mybatis配置文件(mybatis-config-9.xml)
```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">
<!--核心配置文件-->
<!--envrionments可配置多环境-->
<environments default="development">
<!--环境配置一 -->
<environment id="development">
<!--数据库管理器:1.JDBC 2.MANAGED-->
<transactionManager type="JDBC"/>
<!--连接池的实现-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--使用通配符注册绑定配置文件-->
<mapper resource="BlogMapper.xml"/>
</mappers>
- 编写数据库连接池配置文件(db.properties)
```properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8
username=root
password=123456
- 编写实体类(Blog.java) ```java package com.iflytek.pojo;
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString;
import java.util.Date; @Data @NoArgsConstructor @AllArgsConstructor @ToString @SuppressWarnings(“all”) public class Blog { private String id; private String title; private String author; private Date createTtime; //属性名与字段名不一致哦 private int views; }
- 编写实体类接口(BlogManpper.java)
```java
package com.iflytek.Dao;
import com.iflytek.pojo.Blog;
import org.apache.ibatis.annotations.Select;
import java.util.List;
import java.util.Map;
@SuppressWarnings("all")
public interface BlogMapper {
//1.插入记录
int insertBlog(Blog blog);
}
- 编写SQL业务类(BlogMapper.xml) ```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">
<!--插入记录-->
<insert id="insertBlog" parameterType="blog">
insert into blog values(#{id},#{title},#{author},#{createTtime},#{views});
</insert>
- 测试
```java
package com.iflytek.Dao;
import com.iflytek.pojo.Blog;
import com.iflytek.utils.IDutils;
import com.iflytek.utils.MybatisUtils_9;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@SuppressWarnings(“all”)
public class BlogMapperTest {
//1.获取连接对象
SqlSession sqlSession = MybatisUtils_9.getsqlSession();
//2.获取mapper声明
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
//添加记录
@Test
public void getBlogTest(){
//创建执行对象
Blog blog = new Blog();
//添加记录
blog.setId(IDutils.getId());
blog.setTitle("Mybatis");
blog.setAuthor("狂神说");
blog.setCreateTtime(new Date());
blog.setViews(9999);
mapper.insertBlog(blog);
blog.setId(IDutils.getId());
blog.setTitle("Java");
mapper.insertBlog(blog);
blog.setId(IDutils.getId());
blog.setTitle("Spring");
mapper.insertBlog(blog);
blog.setId(IDutils.getId());
blog.setTitle("微服务");
mapper.insertBlog(blog);
sqlSession.commit();
sqlSession.close();
}
}
- 在没有学习MyBatis-Plus之前,动态SQL是后台与数据库交互最简单的实现方式,没有之一。
1、动态SQL-IF
动态IF查询只要用于,多条件查询时灵活切换条件
- 实体类接口
//2.if条件查询之博客查询
List<Blog> queryBlogIF(Map map);
SQL语句(该方法也称为sql引用)
<!--if条件查询-->
<select id="queryBlogIF" parameterType="map" resultType="blog">
select * from blog
<!--where自动匹配条查询条件是否满足-->
<where>
<include refid="if-title-author"/>
</where>
</select>
<!--sql片段插入公共部分,where语法不能使用,实现代码多次复用,提高程序效率-->
<sql id="if-title-author">
<if test="title !=null">
title =#{title}
</if>
<if test="author !=null">
and author =#{author}
</if>
</sql>
测试
//实现动态if条件查询一
@Test
public void quaryBlogTest(){
//1.获取连接对象
SqlSession sqlSession = MybatisUtils_9.getsqlSession();
//2.获取mapper声明
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
//实现动态查询语句的关键
HashMap map = new HashMap();
//实现动态SQL
map.put("title","微服务");
map.put("author","狂神说");
List<Blog> blogs = mapper.queryBlogIF(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
//提交事务
sqlSession.commit();
//释放资源
sqlSession.close();
}
- SQL语句动态拼接,查询什么字段,自动拼接什么字段
2、动态SQL-常用语句
- 实体类接口
//2.if条件查询之博客查询
List<Blog> queryBlogIF(Map map);
- SQL语句(Mapper.xml)
<!--choose条件查询-->
<select id="queryBlogChoose" parameterType="map" resultType="blog">
select *from blog
<where>
<!--choose只要满足所有条件中的靠前的一个就停止查询-->
<choose>
<!--满足条件查询一下内容-->
<when test="title !=null">
title=#{title}
</when>
<when test="author !=null">
and author=#{author}
</when>
<!--不满足条件查询下面内容-->
<otherwise>
and views = #{views}
</otherwise>
</choose>
</where>
</select>
测试
//实现动态choose条件查询一
@Test
public void quaryBlogTest_2() {
//1.获取连接对象
SqlSession sqlSession = MybatisUtils_9.getsqlSession();
//2.获取mapper声明
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
//实现动态查询语句的关键
HashMap map = new HashMap();
// map.put("views",9999);
map.put("title","Java");
List<Blog> blogs = mapper.queryBlogChoose(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
//提交事务
sqlSession.commit();
//释放资源
sqlSession.close();
}
- SQL拼接在多条件查询下只识别第一个条件,不论顺序先后
3、动态SQL-Foreach
- 实体类接口
//查询前三条记录
List<Blog> queryBlogForeach(Map map);
- SQL语句(Mapper.xml)
<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>
- 测试
//实现动态foreach查询
@Test
public void queryBlogForeach(){
//实现动态查询语句的关键
HashMap map = new HashMap();
//执行动态SQL
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.commit();
//释放资源
sqlSession.close();
}
2.5、MyBatis缓存原理
何为缓存?
- 什么是缓存 [ Cache ]?
- 存在内存中的临时数据。
- 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
- 为什么使用缓存?
- 减少和数据库的交互次数,减少系统开销,提高系统效率。
- 什么样的数据能使用缓存?
- 经常查询并且不经常改变的数据。
- 缓存读数据,不缓存写数据。
缓存的多样性
Mamecache
- 一中分布式的高速缓存系统
- 以更小的资源支持更大负载网站的运行,以小博大
- 尽量减少用户等待时间,节省系统资源开销,节省带宽使用
- Memcache内存缓存技术、静态化技术、mysql优化
- Mamecache与Redis的区别与联系
- redis:支持比较多的数据类型(String、list、set、sortset/hash),redis支持集合计算的(set类型支持),每个key最大数据存储量为1G,redis是新兴的内存缓存技术,对各方面支持不完善,支持持久化操作。
- memcache:老牌的内存缓存技术,对相关领域支持比较丰富,window和linux都可以使用,各种框架(tp、yii等等)都支持使用,session的信息可以非常方便的保存到该memcache中,每个key保存的数据量最大为1M,支持的数据类型比较单一,就是String类型,不支持持久化。
EhCache
- 一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认CacheProvider
- 一种广泛使用的开源Java分布式缓存
- 主要面向通用缓存,Java EE和轻量级容器
- 它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点
- 快速、简单、支持多种缓存策略
- 缓存数据有两级:内存和磁盘,因此无需担心容量问题
- 缓存数据会在虚拟机重启的过程中写入磁盘
- 可以通过RMI、可插入API等方式进行分布式缓存
- 具有缓存和缓存管理器的侦听接口
- 支持多缓存管理器实例,以及一个实例的多个缓存区域
- 提供Hibernate的缓存实现
- EhCahcee与Redis的区别与联系
- Redis是通过socket访问到缓存服务,效率比Ehcache低,比数据库要快很多,处理集群和分布式缓存方便,有成熟的方案。
- 如果是单个应用或者对缓存访问要求很高的应用,用ehcache。如果是大型系统,存在缓存共享、分布式部署、缓存内容很大的,建议用redis。
- EhCache直接在jvm虚拟机中缓存,速度快,效率高;但是缓存共享麻烦,集群分布式应用不方便。
- ehcache也有缓存共享方案,不过是通过RMI或者Jgroup多播方式进行广播缓存通知更新,缓存共享复杂,维护不方便;简单的共享可以,但是涉及到缓存恢复,大数据缓存,则不合适。
- Redis是通过socket访问到缓存服务,效率比Ehcache低,比数据库要快很多,处理集群和分布式缓存方便,有成熟的方案。
Redis
- 一个优秀的用于缓存的NoSQL
MyBatis缓存
- MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存,缓存可以极大的提升查询效率。
- MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
- 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
1、一级缓存
- 一级缓存也叫本地缓存:
- 与数据库同一次会话期间查询到的数据会放在本地缓存中。
- 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库。
- sqlSession开启就缓存到本地了
- 一级缓存容易失效
- 查询不同的数据就会更新缓存,导致失效
- 执行增删改查时会改变原来的数据,也会导致缓存失效
- 查询不同的Mapper.xml
- 手动清理缓存导致失效
- 查询内容 ```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">
- 测试
```java
package com.iflytek.Dao;
import com.iflytek.pojo.User;
import com.iflytek.utils.MybatisUtils_10;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
//二级缓存测试多查询事务回滚
public class UserMapperTest {
//1.获取连接对象
SqlSession sqlSession = MybatisUtils_10.getsqlSession();
//2.获取getMapper连接声明
UserMapper mapper= sqlSession.getMapper(UserMapper.class);
@Test
public void queryUserById_Test(){
User user = mapper.queryUserById(1);
System.out.println(user);
//增删改查都会刷新一级缓存机制
System.out.println("-------------------------------------");
sqlSession.close();
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user == user2);
//增删改查都会刷新一级缓存机制
sqlSession.close();
}
}
- 缓存效果
- 执行查询时,缓存已经生成
缓存失效
- 执行CRUD即可导致一级缓存失效 ```xml
update user set name =#{name},pwd=#{pwd},gender=#{gender},height=#{height} where id=#{id};
import com.iflytek.pojo.User; import com.iflytek.utils.MybatisUtils_10; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.session.SqlSession; import org.junit.Test;
//二级缓存测试多查询事务回滚 public class UserMapperTest { //1.获取连接对象 SqlSession sqlSession = MybatisUtils_10.getsqlSession();
//2.获取getMapper连接声明
UserMapper mapper= sqlSession.getMapper(UserMapper.class);
@Test
public void queryUserById_Test(){
User user = mapper.queryUserById(1);
System.out.println(user);
//增删改查都会刷新一级缓存机制
mapper.updateUser(new User(2,"三岁","111111","男",180));
System.out.println("-------------------------------------");
sqlSession.close();
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user == user2);
//增删改查都会刷新一级缓存机制
sqlSession.close();
}
}
- 缓存失效<br />![](https://gitee.com/hg14150/blogiamges/raw/master/img/image-20210415195623543.png#crop=0&crop=0&crop=1&crop=1&id=tvQuR&originHeight=572&originWidth=956&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
- 插入语句插队,导致缓存失效,产生第二次查询,缓存记录清空,所以记录为false
<a name="088daa62"></a>
#### 2、二级缓存
- 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
- 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
- 工作机制
-
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
- 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
- 新的会话查询信息,就可以从二级缓存中获取内容;
- 不同的mapper查出的数据会放在自己对应的缓存(map)中;
- 使用步骤
- 开启全局缓存(mybatis-config.xml)
```xml
<setting name="cacheEnabled" value="true"/>
- 自定义二级缓存设置
<cache
eviction="FIFO" #创建高级缓存机制,first in first out 先进先出原则
flushInterval="60000" #设置刷新时间
size="512" #设置最大存储对象
readOnly="true" #开启返回只读权限
/>
- 测试 ```java package com.iflytek.Dao;
import com.iflytek.pojo.User; import com.iflytek.utils.MybatisUtils_10; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.session.SqlSession; import org.junit.Test;
//二级缓存测试多查询事务回滚 public class UserMapperTest { //1.获取连接对象 SqlSession sqlSession = MybatisUtils_10.getsqlSession(); SqlSession sqlSession2 = MybatisUtils_10.getsqlSession();
//2.获取getMapper连接声明
UserMapper mapper= sqlSession.getMapper(UserMapper.class);
UserMapper mapper2= sqlSession2.getMapper(UserMapper.class);
@Test
public void queryUserById_Test(){
User user = mapper.queryUserById(1);
System.out.println(user);
//增删改查都会刷新一级缓存机制
// mapper.updateUser(new User(2,”小王”,”111111”,”男”,167)); System.out.println(“——————————————————-“); sqlSession.close();
User user2 = mapper2.queryUserById(1);
System.out.println(user2);
System.out.println(user == user2);
//增删改查都会刷新一级缓存机制
sqlSession2.close();
}
}
- 缓存效果<br />![](https://gitee.com/hg14150/blogiamges/raw/master/img/image-20210415204236842.png#crop=0&crop=0&crop=1&crop=1&id=oVRbb&originHeight=494&originWidth=884&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
- 两次查询只有一次记录,证明二级缓存已经生效
- 总结
- 只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据
- 查出的数据都会被默认先放在一级缓存中
- 只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中
<a name="fd1d32b4"></a>
## 三、MyBatis-Plus
<a name="c19cae35"></a>
### 3.1、快速入门MyBats-Plus
- [MyBatis-Plus (opens new window)](https://github.com/baomidou/mybatis-plus)(简称 MP),在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
- 基友搭配,效率翻倍。
> 特性
- **无侵入**:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- **损耗小**:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
- **强大的 CRUD 操作**:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
- **支持 Lambda 形式调用**:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
- **支持主键自动生成**:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
- **支持 ActiveRecord 模式**:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
- **支持自定义全局通用操作**:支持全局通用方法注入( Write once, use anywhere )
- **内置代码生成器**:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
- **内置分页插件**:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
- **分页插件支持多种数据库**:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
- **内置性能分析插件**:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
- **内置全局拦截插件**:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
> 框架结构
![](https://gitee.com/hg14150/blogiamges/raw/master/img/image-20210415212019187.png#crop=0&crop=0&crop=1&crop=1&id=SVlp0&originHeight=614&originWidth=1062&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
<a name="c37031af"></a>
#### 3.1.1、快速搭建环境
<a name="d3277b01"></a>
###### 1、初始化Spring-Boot工程 [Spring Initializer (opens new window)](https://start.spring.io/)
<a name="bc6c489e"></a>
###### 2、导入依赖
```xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
<!--mybaits-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
3、连接配置
#数据库连接配置
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/school?useUnicode=true&characterEncoding=UTF-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
#日志配置,默认日志支持
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
4、使用
- 在配置完实体类接口后要在主方法上添加扫描包,
@MapperScan("com.iflytek.mapper")
```java package com.iflytek;
import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan(“com.iflytek.mapper”) @SpringBootApplication public class MybatisPlus1Application { public static void main(String[] args) { SpringApplication.run(MybatisPlus1Application.class, args); } }
- `BaseMapper<User>`的使用
- 在xxxMapper.java接口中继承 **BaseMapper**父类,可以省略所以简单SQL的CRUD语句,范型是SQL操作需要使用的查询范型,增加后SQL操作中将不需要再手动添加
```java
public interface UserMapper extends BaseMapper<Person> {
void selectList();
}
- 测试 ```java package com.iflytek;
import com.iflytek.mapper.UserMapper; import com.iflytek.pojo.Person; import org.apache.catalina.User; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest class MybatisPlus1ApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
void contextLoads() {
List<Person> personList = userMapper.selectList(null);
personList.forEach(System.out::println);
}
}
- 结果<br />![](https://gitee.com/hg14150/blogiamges/raw/master/img/image-20210416193459291.png#crop=0&crop=0&crop=1&crop=1&id=XQfiN&originHeight=587&originWidth=1416&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
<a name="7121e0e2"></a>
### 3.2、CRUD高阶处理
> 实体类接口继承了**BaseMapper**父类,简单SQL语句可省不写
<a name="77933ad0"></a>
#### 3.2.1、CURUD简单操作
- **Insert插入**
- 测试
```java
//测试数据插入
@Test
public void InsertTest(){
Person person = new Person();
person.setName("二哈");
person.setAge(18);
person.setEmail("123456@qq.com");
int insert = userMapper.insert(person);
System.out.println(insert);
System.out.println(person);
}
- 结果
- Update更新
- 测试
//测试数据更新
@Test
public void UpdataTest(){
Person person = new Person();
person.setId(6L);
person.setName("小黄");
person.setAge(18);
person.setEmail("11445522@qq.com");
int update = userMapper.updateById(person);
System.out.println(update);
System.out.println(person);
}
- 结果
- Delete删除
- 测试
//测试数据删除
@Test
public void DeleteTest(){
Person person = new Person();
person.setId(5L);
int delete = userMapper.deleteById(person);
System.out.println(delete);
System.out.println(person);
}
- 结果
- Select查询
- 测试1,测试多个id的数据
//多数据查询
@Test
public void SelectByIdTest(){
List<Person> personList = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
System.out.println(personList);
}
- 结果1
测试2,条件查询,使用万能的Map更加方便
//条件查询
@Test
public void SelectByOther(){
HashMap<Object, Object> map = new HashMap<>();
//定义SQL
map.put("name","李四");
List<Person> persons= userMapper.selectByMap(map);
System.out.println(persons);
}
- 结果2
3.2.2、雪花算法及主键策略
3.2.2.1、雪花算法
- snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。
- 最常见的方式。利用数据库,全数据库唯一。
- 优点:
1)简单,代码方便,性能可以接受。
2)数字ID天然排序,对分页或者需要排序的结果很有帮助。 - 缺点:
1)不同数据库语法和实现不同,数据库迁移的时候或多数据库版本支持的时候需要处理。
2)在单个数据库或读写分离或一主多从的情况下,只有一个主库可以生成,有单点故障的风险。
3)在性能达不到要求的情况下,比较难于扩展。
4)如果遇见多个系统需要合并或者涉及到数据迁移会相当痛苦。
5)分表分库的时候会有麻烦。
- 优点:
- 在id自动填充的阵营里,还有UUID,Redis,zookeeper等都能达到唯一标准的效果
雪花算法的实现
public class IdWorker{
//下面两个每个5位,加起来就是10位的工作机器id
private long workerId; //工作id
private long datacenterId; //数据id
//12位的序列号
private long sequence;
public IdWorker(long workerId, long datacenterId, long sequence){
// sanity check for workerId
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0",maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0",maxDatacenterId));
}
System.out.printf("worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d",
timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId);
this.workerId = workerId;
this.datacenterId = datacenterId;
this.sequence = sequence;
}
//初始时间戳
private long twepoch = 1288834974657L;
//长度为5位
private long workerIdBits = 5L;
private long datacenterIdBits = 5L;
//最大值
private long maxWorkerId = -1L ^ (-1L << workerIdBits);
private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
//序列号id长度
private long sequenceBits = 12L;
//序列号最大值
private long sequenceMask = -1L ^ (-1L << sequenceBits);
//工作id需要左移的位数,12位
private long workerIdShift = sequenceBits;
//数据id需要左移位数 12+5=17位
private long datacenterIdShift = sequenceBits + workerIdBits;
//时间戳需要左移位数 12+5+5=22位
private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
//上次时间戳,初始值为负数
private long lastTimestamp = -1L;
public long getWorkerId(){
return workerId;
}
public long getDatacenterId(){
return datacenterId;
}
public long getTimestamp(){
return System.currentTimeMillis();
}
//下一个ID生成算法
public synchronized long nextId() {
long timestamp = timeGen();
//获取当前时间戳如果小于上次时间戳,则表示时间戳获取出现异常
if (timestamp < lastTimestamp) {
System.err.printf("clock is moving backwards. Rejecting requests until %d.", lastTimestamp);
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds",
lastTimestamp - timestamp));
}
//获取当前时间戳如果等于上次时间戳(同一毫秒内),则在序列号加一;否则序列号赋值为0,从0开始。
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0;
}
//将上次时间戳值刷新
lastTimestamp = timestamp;
/**
* 返回结果:
* (timestamp - twepoch) << timestampLeftShift) 表示将时间戳减去初始时间戳,再左移相应位数
* (datacenterId << datacenterIdShift) 表示将数据id左移相应位数
* (workerId << workerIdShift) 表示将工作id左移相应位数
* | 是按位或运算符,例如:x | y,只有当x,y都为0的时候结果才为0,其它情况结果都为1。
* 因为个部分只有相应位上的值有意义,其它位上都是0,所以将各部分的值进行 | 运算就能得到最终拼接好的id
*/
return ((timestamp - twepoch) << timestampLeftShift) |
(datacenterId << datacenterIdShift) |
(workerId << workerIdShift) |
sequence;
}
//获取时间戳,并与上次时间戳比较
private long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
//获取系统时间戳
private long timeGen(){
return System.currentTimeMillis();
}
//---------------测试---------------
public static void main(String[] args) {
IdWorker worker = new IdWorker(1,1,1);
for (int i = 0; i < 30; i++) {
System.out.println(worker.nextId());
}
}
}
3.2.2.2、主键生成策略
- 在java层面的主键生成策略主要依赖于
TableID
注解的方法和属性@TableId(type = IdType.AUTO)
private Long id;
TableId基础方法
```java IdType type() default IdType.NONE;
public enum IdType { AUTO(0), //主键自增,数据库id必须设置自增,不然会报错 NONE(1), //不使用注解 INPUT(2), //手动输入 ID_WORKER(3), //全局唯一ID UUID(4), //生成UUID ID_WORKER_STR(5); //截取字符串
private int key;
private IdType(int key) {
this.key = key;
}
public int getKey() {
return this.key;
}
}
<a name="a49693da"></a>
##### 3.2.2.3、自动填充
- 自动填充主要用于数据创建时间、数据修改时间等出事操作
- 在阿里巴巴开发手册里要求使用
- gmt_create
- gmt_modified
- 方式一:数据库层面的自动填充(**工作中不建议使用**)
- create_time 自动填充创建时间
- update_time 自动填充修改时间<br />![](https://gitee.com/hg14150/blogiamges/raw/master/img/image-20210416205428966.png#crop=0&crop=0&crop=1&crop=1&id=THnLY&originHeight=327&originWidth=1306&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
- 同步实体类
```java
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Person implements Serializable {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private int age;
private String email;
private Date createTime;
private Date updateTime;
}
测试
//测试数据插入
@Test
public void InsertTest(){
Person person = new Person();
person.setName("陈皮");
person.setAge(18);
person.setEmail("123456@qq.com");
int insert = userMapper.insert(person);
System.out.println(insert);
System.out.println(person);
}
//测试数据更新
@Test
public void UpdataTest(){
Person person = new Person();
person.setId(6L);
person.setName("老狗");
person.setAge(18);
person.setEmail("11445521232@qq.com");
int update = userMapper.updateById(person);
System.out.println(update);
System.out.println(person);
}
- 结果
- 方法二:java层面的自动填充
- Java层面的自动填充操作需要在实体类上添加注解,一般企业级开发建议操作
TableFild
介绍 ```java //默认为null String value() default “”;
public enum FieldFill { DEFAULT, //默认使用 INSERT, //插入时使用 UPDATE, //更新时使用 INSERT_UPDATE;//插入和更新时使用
private FieldFill() {
}
}
- 使用
1. 添加注解
```java
/**
* 字段自动填充
*/
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
2. 处理注解配置,新建一个`Handler.java`类实现`MetaObjectHandler`父类
package com.iflytek.Config;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* @author 黄果
* @date2021/4/16 21:07
* @title 现世安稳,岁月静好,佛祖保佑,永无bug!
*/
@Slf4j
@Component //添加到IOC容器里
public class Handler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
//插入时自动填充
this.setFieldValByName("createTime",new Date(),metaObject);
log.info("执行插入填充");
}
@Override
public void updateFill(MetaObject metaObject) {
//更新时自动填充
this.setFieldValByName("updateTime",new Date(),metaObject);
log.info("执行更新填充");
}
}
3. 测试
//测试数据插入
@Test
public void InsertTest(){
Person person = new Person();
person.setName("八戒");
person.setAge(18);
person.setEmail("369820440@qq.com");
int insert = userMapper.insert(person);
System.out.println(insert);
System.out.println(person);
}
//测试数据更新
@Test
public void UpdataTest(){
Person person = new Person();
person.setId(5L);
person.setName("小王");
person.setAge(18);
person.setEmail("11445521232@qq.com");
int update = userMapper.updateById(person);
System.out.println(update);
System.out.println(person);
}
4. 结果<br />![](https://gitee.com/hg14150/blogiamges/raw/master/img/image-20210416212140244.png#crop=0&crop=0&crop=1&crop=1&id=u96nO&originHeight=288&originWidth=1426&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)<br />![](https://gitee.com/hg14150/blogiamges/raw/master/img/image-20210416212235542.png#crop=0&crop=0&crop=1&crop=1&id=USPMQ&originHeight=423&originWidth=1556&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)<br />![](https://gitee.com/hg14150/blogiamges/raw/master/img/image-20210416212342113.png#crop=0&crop=0&crop=1&crop=1&id=TokfH&originHeight=328&originWidth=1265&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
3.3、插件应用分析
3.3.1、乐观锁处理
- 乐观锁
- 平时不加锁,出问题了在更新测试,容错率高
- 悲观锁
- 新建数据时即加锁,只要一出问题就锁定数据,过于严谨
乐观锁实现
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
```
乐观锁:1、先查询,获取版本号 version=1
A
update person set name =’小黄’ ,version =version + 1 where id = 2 and version =1
B
线程抢先完成,version =2 导致A失败
update person set name =’春花’ ,version =version + 1 where id = 2 and version =1
- 测试乐观锁插件
1. 添加数据库字段<br />![](https://gitee.com/hg14150/blogiamges/raw/master/img/image-20210416220011584.png#crop=0&crop=0&crop=1&crop=1&id=AQh8j&originHeight=308&originWidth=1263&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
1. 添加实体类字段及注解
```java
/**
* 添加乐观锁注解
*/
@Version
private int version;
注册乐观锁插件,新建MyBatisPlusConfig.java类
//注册乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
测试
//测试乐观锁——成功
@Test
public void OptimisticLockTest(){
//查询用户信息
Person person = userMapper.selectById(1L);
//修改数据
person.setName("悟空");
person.setAge(25);
//执行修改
int update = userMapper.updateById(person);
System.out.println(update);
System.out.println(person);
}
//测试乐观锁——成功
@Test
public void OptimisticLockTest2(){
//查询用户信息
//线程1
Person person = userMapper.selectById(1L);
//修改数据
person.setName("悟空");
person.setAge(25);
//线程2插队
//线程1
Person person2 = userMapper.selectById(1L);
//修改数据
person2.setName("盲僧");
person2.setAge(28);
//执行线程2插队修改
int update2 = userMapper.updateById(person2);
System.out.println(update2);
System.out.println(person2);
//执行线程1修改
int update = userMapper.updateById(person);
System.out.println(update);
System.out.println(person);
}
- 结果
成功
失败
- 数据显示
3.3.2、分页的实现
分页是一个最热的业务基础,几乎所有的网站运用都要用到,经常使用的分页技术以下所示
- Limit实现分页
- RowBounds实现分页
- MyBatis-plus实现分页,本质还是limit
MyBatis-Plus分页的使用
- 注册分页插件(MyBatisPlusConfig.java)
//注册分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
// paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
- 测试
@Test
public void PageTest(){
//定义当前查询页的起始id和大小
Page<Person> page = new Page<>(1,3);
IPage<Person> personIPage = userMapper.selectPage(page, null);
System.out.println(personIPage.getRecords());
}
- 结果
3.3.3、删除操作
1、普通删除
- 测试1,通过id删除多个数据
//通过id删除多个数据
@Test
public void DeleteByBatchIdTest(){
int deleteBatchIds = userMapper.deleteBatchIds(Arrays.asList(1, 2));
System.out.println(deleteBatchIds);
}
- 结果1
- 测试2,通过map删除数据
//通过Map删除数据
@Test
public void DeleteByMapest(){
HashMap<String, Object> map = new HashMap<>();
map.put("name","老狗");
int deleteByMap = userMapper.deleteByMap(map);
System.out.println(deleteByMap);
}
- 结果2
2、逻辑删除
- 物理删除
- 删除就没有了,从磁盘里摸出数据,不可恢复
- 逻辑删除
- 只是逻辑形式上的删除,使用一个变量让数据失效,让用户查询不到数据的的存在,实际上数据并没有真正的删除,依然还存在数据库中
删除操作
- 增加数据库伪删除字段
deleted
且默认值为0
- 实体类添加字段及注解
/**
* 逻辑删除
*/
@TableLogic
private int deleted;
- 注册逻辑删除插件(MyBatisPlusConfig.java / application.properties)
//注册逻辑删除插件
@Bean
public ISqlInjector sqlInjector(){
return new LogicSqlInjector();
}
- 测试
//通过Map删除多个数据,执行逻辑删除
@Test
public void DeleteByMapest(){
HashMap<String, Object> map = new HashMap<>();
map.put("id",3);
int deleteByMap = userMapper.deleteByMap(map);
System.out.println(deleteByMap);
}
- 结果
数据显示
3.3.4、性能分析
用于监管每条数据读写的时间大小,从而反应出性能的优劣程度
- 注册插件(MyBatisPlusConfig.java)
//注册性能插件
@Bean
// 设置 dev test 环境开启
@Profile({"dev","test"})
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
//ms,超过此处设置的ms则sql不执行
performanceInterceptor.setMaxTime(50);
//是否格式化SQL语句
performanceInterceptor.setFormat(true);
return performanceInterceptor;
}
- 开启测试环境
#设置开发环境
spring.profiles.active=dev
- 测试
//测试查询全部数据
@Test
void selectListTest() {
List<Person> personList = userMapper.selectList(null);
personList.forEach(System.out::println);
}
- 结果1,最大时间过小,待优化
- 结果2,时间适当,最优查询
3.3.5、条件构造器Wrapper
这是一个敏捷开发的重要工具,一切为了简化而生是它的使命
| Wapper | Maens | Users | | —- | —- | —- | | eq | = |eq("name", "老王")
,name = '老王'
| | ne | != |ne("name", "老王")
,name != '老王'
| | gt | > |gt("age", 18)
,age > 18
| | lt | < |lt("age", 18)
,age < 18
| | between | 1 and 2 |between("age", 18, 30)
,age between 18 and 30
| | notBetween | not 1 and 2 |notBetween("age", 18, 30)
,age not between 18 and 30
| | like | 模糊查询 |likeRight("name", "王")
,name like '王%'
| | nolike | 模糊查询外的数据 |notLike("name", "王")
,name not like '%王%'
| | likeLeft | 左模糊查询 |likeRight("name", "王")
,name like '王%'
| | likeRight | 右模糊查询 |likeLeft("name", "王")
,name like '%王'
| | isNull | 字段为空 |isNull("name")
,name is null
| | isNotNull | 字段不为空 |isNotNull("name")
,name is not null
| | in | 字段内查询 |in("age",{1,2,3})
,age in (1,2,3)
| | inSql | 嵌套查询 |inSql("id", "select id from table where id < 3")
,id in (select id from table where id < 3)
| | groupBy | 字段分组 |groupBy("id", "name")
,group by id,name
| | orderByAsc | 升序 | orderByAsc(“id”),
order by id ASC| | orderByDesc | 降序 |
orderByDesc(“id”)<br />,
order by id DESC| | orderBy | 字段排序 |
orderBy(true, true, “id”, “name”)<br />,
order by id ASC,name ASC| | having | 函数having(sql) |
having(“sum(age) > 10”)<br />,
having sum(age) > 10` |测试1
//测试查询全部数据
@Test
public void selectListTest() {
//查询name不为空的,邮箱不为空的用户
QueryWrapper<Person> wapper = new QueryWrapper<>();
wapper.isNotNull("name").isNotNull("email").ge("age",20);
userMapper.selectList(wapper);
System.out.println(wapper);
}
- 结果1
- 测试2
//查询年龄在20到25之间的数据
@Test
public void betweenTest(){
QueryWrapper<Person> wapper = new QueryWrapper<>();
wapper.between("age", 20, 25);
int count=userMapper.selectCount(wapper);
List<Person> personList = userMapper.selectList(wapper);
System.out.println(count);
System.out.println(personList);
}
- 结果2
- 测试3
//查询指定数据
@Test
public void EqTest(){
QueryWrapper<Person> wappers = new QueryWrapper<>();
QueryWrapper<Person> eq = wappers.eq("name", "八戒");
Person person = userMapper.selectOne(eq);
System.out.println(person);
}
- 结果3
- 测试4
//模糊查询不包含指定内容的数据
@Test
public void noLikeTest(){
QueryWrapper<Person> wapper = new QueryWrapper<>();
wapper.notLike("name","小").likeRight("email","t");
List<Map<String, Object>> maps = userMapper.selectMaps(wapper);
maps.forEach(System.out::println);
}
- 结果4
- 测试5
//模糊自查询
@Test
public void selectTest(){
QueryWrapper<Person> wapper = new QueryWrapper<>();
wapper.inSql("id","select id from person where id>6");
List<Person> personList = userMapper.selectList(wapper);
personList.forEach(System.out::println);
}
- 结果5
- 测试6
//排序测试
@Test
public void oderbyscTest(){
QueryWrapper<Person> wapper = new QueryWrapper<>();
wapper.orderByDesc("id");
List<Person> personList = userMapper.selectList(wapper);
personList.forEach(System.out::println);
}
- 结果6
- 开发中使用能在一定程度上提高工作效率
3.5、MyBatis-Plus逆向工程
这才是重点
1、创建springBoot工程
2、导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
<!--mybaits-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
</dependencies>
3、配置数据库连接(application.properties)
#设置开发环境
spring.profiles.active=dev
#数据库连接配置
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/school?useUnicode=true&characterEncoding=UTF-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
#日志配置
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#配置逻辑删除
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
4、配置插件注册(MyBatisPlusConfig.java)
//注册乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
//注册分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
// paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
//注册逻辑删除插件
@Bean
public ISqlInjector sqlInjector(){
return new LogicSqlInjector();
}
//注册性能插件
@Bean
// 设置 dev test 环境开启
@Profile({"dev","test"})
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(50);
//ms,超过此处设置的ms则sql不执行
//performanceInterceptor.setFormat(true);
return performanceInterceptor;
}
5、配置自动生成类(CodeGenerator.java)
1、控制台输入
public class CodeGenerator {
//0、读取控制台内容
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotBlank(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
public static void main(String[] args) {
//1、获取代码生成器对象
AutoGenerator mpg = new AutoGenerator();
//2、全局配置
GlobalConfig gc = new GlobalConfig();
//获取用户目录
String projectPath = System.getProperty("user.dir");
//代码生成路径
gc.setOutputDir(projectPath + "/src/main/java");
//作者信息
gc.setAuthor("黄果");
gc.setOpen(false);
//生成ID类型
gc.setIdType(IdType.AUTO);
gc.setDateType(DateType.ONLY_DATE);
gc.setFileOverride(true);
//去掉Servce前缀
gc.setServiceImplName("%sService");
mpg.setGlobalConfig(gc);
//3、数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/school?useUnicode=true&useSSL=false&characterEncoding=utf8");
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
//4、包配置
PackageConfig pc = new PackageConfig();
//设置模块名 com.iflytek.project
//pc.setModuleName("project"); //没有第0步时此代码必写
pc.setModuleName(scanner("模块名")); //第0步省略时此代码可删
//5、设置工程名
pc.setParent("com.iflytek");
//设置包名
pc.setEntity("entity");
pc.setMapper("mapper");
pc.setService("service");
pc.setController("controller");
mpg.setPackageInfo(pc);
//6、自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
//7、如果模板引擎是 freemarker
String templatePath = "/templates/mapper.xml.ftl";
//8、自定义输出配置
List<FileOutConfig> focList = new ArrayList<>();
// 自定义配置会被优先输出
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()
+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
//9、配置模板
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setXml(null);
mpg.setTemplate(templateConfig);
//10、策略配置
StrategyConfig strategy = new StrategyConfig();
//数据库表名优先映射,配置
strategy.setInclude("person");
//下划线转驼峰命名的策略
strategy.setNaming(NamingStrategy.underline_to_camel);
//数据库命名规则
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
//自动生成lombok注解
strategy.setEntityLombokModel(true);
//自动添加控制结构
strategy.setRestControllerStyle(true);
//自动添加逻辑删除策略
strategy.setLogicDeleteFieldName("deleted");
//11、设置自动填充策略
TableFill gmtCreatereate = new TableFill("gmt_create", FieldFill.INSERT);
//设置自动更新时间策略
TableFill gmtModifiedodified = new TableFill("gmt_modified", FieldFill.UPDATE);
//获取自动填充对象
ArrayList<TableFill> tableFills = new ArrayList<>();
//添加填充策略
tableFills.add(gmtCreatereate);
tableFills.add(gmtModifiedodified);
strategy.setTableFillList(tableFills);
//12、设置乐观锁策略
strategy.setVersionFieldName("version");
//设置Restful风格
strategy.setRestControllerStyle(true);
//设置连接请求 http://localhost:8080/hello_id_2
strategy.setControllerMappingHyphenStyle(true);
//第0步省略,此行代码可删
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
mpg.setStrategy(strategy);
mpg.execute();
}
}
2、无控制台输入
public class CodeGenerator {
public static void main(String[] args) {
//1、获取代码生成器对象
AutoGenerator mpg = new AutoGenerator();
//2、全局配置
GlobalConfig gc = new GlobalConfig();
//获取用户目录
String projectPath = System.getProperty("user.dir");
//代码生成路径
gc.setOutputDir(projectPath + "/src/main/java");
//作者信息
gc.setAuthor("黄果");
gc.setOpen(false);
//生成ID类型
gc.setIdType(IdType.AUTO);
gc.setDateType(DateType.ONLY_DATE);
gc.setFileOverride(true);
//去掉Servce前缀
gc.setServiceImplName("%sService");
mpg.setGlobalConfig(gc);
//3、数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/school?useUnicode=true&useSSL=false&characterEncoding=utf8");
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
//4、包配置
PackageConfig pc = new PackageConfig();
//设置模块名 com.iflytek.project
pc.setModuleName("project"); //没有第0步时此代码必写
//5、设置工程名
pc.setParent("com.iflytek");
//设置包名
pc.setEntity("entity");
pc.setMapper("mapper");
pc.setService("service");
pc.setController("controller");
mpg.setPackageInfo(pc);
//6、策略配置
StrategyConfig strategy = new StrategyConfig();
//数据库表名优先映射,配置
strategy.setInclude("person","account","student","users");
//下划线转驼峰命名的策略
strategy.setNaming(NamingStrategy.underline_to_camel);
//数据库命名规则
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
//自动生成lombok注解
strategy.setEntityLombokModel(true);
//自动添加控制结构
strategy.setRestControllerStyle(true);
//自动添加逻辑删除策略
strategy.setLogicDeleteFieldName("deleted");
//7、设置自动填充策略
TableFill gmtCreatereate = new TableFill("gmt_create", FieldFill.INSERT);
//设置自动更新时间策略
TableFill gmtModifiedodified = new TableFill("gmt_modified", FieldFill.UPDATE);
//获取自动填充对象
ArrayList<TableFill> tableFills = new ArrayList<>();
//添加填充策略
tableFills.add(gmtCreatereate);
tableFills.add(gmtModifiedodified);
strategy.setTableFillList(tableFills);
//8、设置乐观锁策略
strategy.setVersionFieldName("version");
//设置Restful风格
strategy.setRestControllerStyle(true);
//设置连接请求 http://localhost:8080/hello_id_2
strategy.setControllerMappingHyphenStyle(true);
mpg.setStrategy(strategy);
mpg.execute();
}
}