MyBatis与MyBatis-Plus
MyBatis
简介
MyBatis是一款优秀的持久层框架。它支持定制化SQL、存储过程以及高级映射。
MyBatis免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。MyBatis可以通过简单的XML或注解来配置和映射原始类型、接口和Java POJO(Plain Old Java Objects,普通老式Java对象)为数据库中的记录。
持久化
- 持久化就是将程序的数据在持久状态和瞬时状态转化的过程。
- 由于内存有断电之后数据就会丢失的特点,有一些对象不能丢失,需要将其持久化。
持久层
- 完成持久化工作的代码块就是持久层。
传统的JDBC代码太复杂,MyBatis简化了将数据存入数据库这一过程。MyBatis将sql和代码分离,提高了可维护性;提供了映射标签,支持对象与数据库的字段关系的映射(ORM,Object Relation Mapping);提供xml标签,支持编写动态sql。
写一个MyBatis程序
搭建数据库
1 | CREATE DATABASE IF NOT EXISTS mybatis; |
新建项目
导入相关依赖
1 | <dependencies> |
编写核心配置文件
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
XML 配置文件中包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源(DataSource)以及决定事务作用域和控制方式的事务管理器(TransactionManager)。
1 |
|
编写mybatis工具类
1 | /** |
编写代码
实体类
1 | public class User { |
DAO接口
1 | public interface UserDao { |
接口实现类由原来的UserDaoImpl转变为一个Mapper配置文件
1 |
|
测试
1 | public class UserDaoTest { |
增加CRUD
将UserDao变更为UserMapper。
id
对应
namespace
中的方法名。parameterType
参数类型。
resultType
sql语句执行的返回类型。返回值类型是
List
时,resultType填写**List
中存放的对象类型**。
1 | <select id="getUserById" parameterType="int" resultType="com.hunter.pojo.User"> |
测试
注意:增删改需要提交事务
1 |
|
用 Map 传参
如果实体类或数据库中的表,**字段或者参数过多,可以考虑使用Map:parameterType="map"
**,传参geng。
1 | /** |
1 | <!-- parameterType 为 map 时,属性名无需和数据库表的字段一一对应 --> |
1 | try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) { |
配置解析
核心配置文件
官方建议的文件名:mybatis-config.xml
各标签的顺序必须如下。
<configuration> (配置)
- <properties> (属性)
- <settings> (设置)
- <typeAliases> (类型别名)
- <typeHandlers> (类型处理器)
- <objectFactory> (对象工厂)
- <plugins> (插件)
- mybatis-generator-core
- mybatis-plus
- 通用mapper
- <environments> (环境配置)
- <environment> (环境变量)
- <transactionManager> (事务管理器)
- <dataSource> (数据源)
- <environment> (环境变量)
- <mappers> (映射器)
环境配置 environments
<transactionManager>
MyBatis中有两种类型的事务管理器(type=”JDBC|MANAGED”)
如果使用Spring + MyBatis,则没有必要配置事务管理器。
<dataSource>
UNPOOLED
POOLED
(默认)JNDI
属性 properties
可以通过properties
属性来实现引用配置文件。
这些属性都是可外部配置且动态替换的,既可以在典型的Java属性文件中(如db.properties
),也可通过properties
元素的子元素(property
)来传递。
编写一个配置文件db.properties
1 | <!-- 引入外部配置文件, |
类型别名 typeAliases
类型别名是为Java类型设置一个短的名字,用于减少类完全限定名的冗余。
1
2
3
4<!-- 给实体类起别名 -->
<typeAliases>
<typeAlias type="com.hunter.pojo.User" alias="User"/>
</typeAliases>也可以指定一个包名,MyBatis会在包名下搜索需要的Java Bean,比如扫描实体类的包,它的别名就是首字母小写的类名。
1
2
3
4<!-- 指定包名 -->
<typeAliases>
<package name="com.hunter.pojo"/>
</typeAliases>这种方式无法自定义别名,如果要自定义,需要在实体类上增加注解:
1
2
3
4import org.apache.ibatis.type.Alias;
public class User {...}
设置 settings
可以指定MyBatis所用日志的具体实现,未指定时,将自动查找。
1
2
3<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
映射器 mappers
方式一:注册mapper.xml文件【推荐使用】
1
2
3
4<!-- 每个mapper.xml都需要在该配置文件中注册-->
<mappers>
<mapper resource="UserMapper.xml"/>
</mappers>方式二:使用class文件绑定注册
1
2
3<mappers>
<mapper class="com.hunter.dao.UserMapper"/>
</mappers>- 接口和Mapper配置文件必须同名
- 接口和Mapper配置文件必须在同一个包下
方式三:使用扫描包进行注册绑定
1
2
3<mappers>
<package name="com.hunter.dao"/>
</mappers>- 接口和Mapper配置文件必须同名
- 接口和Mapper配置文件必须在同一个包下
生命周期和作用域
生命周期和作用域至关重要,错误的使用会导致严重的并发问题。
SqlSessionFactoryBuilder
一旦创建了SqlSessionFactory,就不再需要它,因此作为局部变量即可。
SqlSessionFactory
可以理解为数据库连接池,一旦被创建,就应该在应用运行期间一直存在,没有任何理由丢弃或重新创建另一个实例,多次重建SqlSessionFactory被视为一种代码坏习惯。因此最简单的就是使用单例模式或者静态单例模式。
SqlSession
连接到连接池的一个请求。用完之后需要马上关闭,避免资源的占用。
结果集映射 resultMap
可以解决对象的属性名和数据库中字段名不一致的问题。
1 | <mapper namespace="com.hunter.dao.UserMapper"> |
resultMap
是Mybatis中最强大的元素。其设计思想是,简单的语句不需要配置显式的结果映射,复杂的语句只需要描述它们的关系。但是现实不仅仅只有这么简单的关系。
日志工厂
初学者定位异常的手段,通常是控制台输出和debug,而日志定位是现实项目的成熟方式。
STDOUT_LOGGING 标准日志输出
1
2
3<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
LOG4J
Log4j是Apache的开源项目,可以控制日志信息输送的目的地是控制台、文件、GUI组件。
- 可以控制每条日志的输出格式。
- 通过定义每条日志信息的级别,能够更加细致地控制日志的生成过程。
- 通过配置文件来灵活地配置,不需要修改应用的代码。
导包
1
2
3
4
5<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.20.0</version>
</dependency>创建
log4j.properties
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29# 将等级为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/mybatis_learning.log
# 每个log文件的大小,超过之后会生成一个新的日志,KB、MB、GB
log4j.appender.file.MaxFileSize=1MB
log4j.appender.file.Threshold=DEBUG
# 输出的文件格式
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=【%p】【%d{yyyy-MM-dd hh:mm:ss}】【%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为日志的实现
1
2
3<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
简单使用
在要使用log4j的类中,导入包
org.apache.log4j.Logger
日志对象,参数为当前类的class
1
public static Logger logger = Logger.getLogger(UserMapperTest.class);
日志级别
1
2
3logger.info("进入了test log4j");
logger.debug("进入了test log4j");
logger.error("进入了test log4j");
分页
分页能减少数据的处理量。
方式一 使用MySQL中的LIMIT语句
1 | SELECT prod_name |
MySQL检索出来的第一行是行0而不是行1。
接口
1
2
3
4
5
6/**
* 分页查询用户
*
* @return List<User>
*/
List<User> getUserListByLimit(Map<String, Integer> map);Mapper.xml
1
2
3
4
5
6<!-- 分页 -->
<select id="getUserListByLimit" parameterType="map" resultType="user">
SELECT *
FROM user
LIMIT #{startIndex}, #{pageSize};
</select>测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void should_success_when_get_user_list_by_limit() {
// 获得sqlSession对象,try语句在结束时会自动关闭资源,这称为try-with-resources语句
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
// 执行sql
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
Map<String, Integer> map = new HashMap<>();
map.put("startIndex", 1);
map.put("pageSize", 2);
List<User> userList = userMapper.getUserListByLimit(map);
for (User user : userList) {
System.out.println(user);
}
}
}
方式二 RowBounds
接口
1
2
3
4
5
6/**
* 分页查询用户
*
* @return List<User>
*/
List<User> getUserListByRowBounds();Mapper.xml
1
2
3
4
5<!-- 分页 -->
<select id="getUserListByRowBounds" resultType="user">
SELECT *
FROM user;
</select>测试
1
2
3
4
5
6
7
8
9
10
11
12
public void should_success_when_get_user_list_by_row_bounds() {
// 获得sqlSession对象,try语句在结束时会自动关闭资源,这称为try-with-resources语句
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
RowBounds rowBounds = new RowBounds(1, 2);
List<User> userList = sqlSession.selectList("com.hunter.dao.UserMapper.getUserListByRowBounds", null, rowBounds);
for (User user : userList) {
System.out.println(user);
}
}
}
方式三 分页插件 pagehelper
1 | <dependency> |
使用注解开发
在真正的开发中,很多时候我们会选择面向接口编程,因为面向接口是一种解耦的方式,易扩展、能提高复用率。
分层开发中,上层不用关注具体的实现,大家都遵守共同的标准,使得开发变得容易,规范性更好。
注解在接口上的实现
1
2
3
4
5
6
7
8
9
10
11
List<User> getUserList();
int addUser(User user);
int updateUser(User user);
int deleteUser(int id);需要在核心配置文件中绑定接口
1
2
3<mappers>
<mapper class="com.hunter.dao.UserMapper"/>
</mappers>
关于@Param()
注解
- 基本类型的参数或者
String
类型,需要加上 - 引用类型不需要加
- 如果只有一个基本类型,可以忽略,但是建议还是加上
- 在SQL中引用的,就是在
@Param()
中设定的属性名
Lombok
Lombok项目其实就是一个java库,它可以自动插入到编辑器和构建工具中,增强java的性能。以后你只需要一些简单的注解,就可以再也不用在类中反复去写getter、equals等方法,让代码变得简介,不用过多地去关注相应的方法。属性修改时,也简化了维护这些属性所生成的get、set方法。
使用方式:
IDEA下载Lombok插件
项目中导入Lombok的jar包
1
2
3
4
5<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
</dependency>在实体类上使用Lombok注解
Lombok注解
@Data
自动创建无参构造函数、get、set、toString、hashcode、equals方法
@AllArgsConstructor
自动创建所有参数的构造函数
@NoArgsConstructor
自动创建无参构造函数
@EqualsAndHashCode
@ToString
@Getter
/@Setter
@Builder
可以类似于链式地调用对象进行赋值
复杂查询
搭建数据库表
1 | CREATE TABLE `teacher` ( |
测试环境搭建
- 导入lombok
- 新建实体类Teacher、Student
- 建立Mapper接口
- 建立Mapper.xml
- 在核心配置文件中绑定注册Mapper.xml文件(方式很多)
- 测试查询
多对一处理
举例:多个学生归属一个老师(如班主任概念)
方式一 嵌套查询处理
1 | <!-- namespace 会绑定一个对应的DAO/Mapper接口 --> |
方式二 联表查询处理
1 | <!-- namespace 会绑定一个对应的DAO/Mapper接口 --> |
一对多处理
举例:一个老师拥有多个学生
联表查询
1 | <mapper namespace="com.hunter.dao.TeacherMapper"> |
动态SQL
动态SQL指根据不同的条件,生成不同的SQL语句。其实动态SQL就是在拼接SQL语句,只要保证SQL的正确性,按照SQL的格式去排列组合就可以了。
搭建数据库表
1 | CREATE TABLE `blog`( |
测试环境搭建
导入lombok
新建实体类Blog
建立Mapper接口
建立Mapper.xml
在核心配置文件中绑定注册class文件BlogMapper(方式很多)
开启自动提交事务
1
2
3
4public static SqlSession getSqlSession() {
// autoCommit:自动开启事务
return sqlSessionFactory.openSession(true);
}初始化数据库表数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public class BlogMapperTest {
public void initBlog() {
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
mapper.addBlog(generateNewBlog(IdUtils.generateId(), "Mybatis学习", "Hunter", new Date(), 0));
mapper.addBlog(generateNewBlog(IdUtils.generateId(), "Java学习", "Hunter", new Date(), 0));
mapper.addBlog(generateNewBlog(IdUtils.generateId(), "Spring学习", "Hunter", new Date(), 0));
mapper.addBlog(generateNewBlog(IdUtils.generateId(), "微服务学习", "Hunter", new Date(), 0));
}
}
private Blog generateNewBlog(String id, String title, String author, Date date, int views) {
Blog blog = new Blog();
blog.setId(id);
blog.setTitle(title);
blog.setAuthor(author);
blog.setCreateTime(date);
blog.setViews(views);
return blog;
}
}
if语句
1 | <select id="queryBlogIf" parameterType="map" resultType="blog"> |
choose, when, otherwise语句
1 | <select id="queryBlogChoose" parameterType="map" resultType="blog"> |
set语句
1 | <update id="updateBlog" parameterType="map"> |
sql片段
使用sql标签抽取出公共的部分,在使用的地方使用include标签引用即可。
- 最好基于单表来定义sql片段
- 不要存在where标签
1 | <sql id="if-title-author"> |
foreach
1 | <!-- SELECT * FROM blog WHERE (id = 1 OR id = 2 OR id = 3) --> |
缓存
缓存是存在内存中的临时数据,将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从而提高查询效率,一定程度上解决了高并发系统的性能问题。
经常查询并且不经常改变的数据适合缓存。
MyBatis中定义了两级缓存:一级缓存和二级缓存。默认情况下,只开启了一级缓存(SqlSession级别的缓存,也称为本地缓存)。二级缓存是基于namespace级别的缓存。为了提高扩展性,MyBatis定义了缓存接口Cache,可以通过实现Cache接口来自定义二级缓存。
一级缓存
一级缓存,也叫本地缓存,是SqlSession级别的缓存,它就是一个map,默认开启。
- 与数据库同一次会话期间查询到的数据会放在本地缓存中。
- 之后如果需要获取相同的数据,直接从缓存中拿,没必要再次访问数据库。
缓存存在的例子:
1 |
|
从日志可以看出,只有一次访问数据库的记录。
缓存失效的情况:
对数据库进行增删改操作,由于可能会改变原来的数据,缓存会刷新。
手动清理
1
2// 手动清理缓存
sqlSession.clearCache();
例子:
1 |
|
二级缓存
二级缓存,也叫全局缓存,是namespace级别的缓存,一个命名空间,对应一个二级缓存。
- 工作机制:
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
- 如果当前会话关闭,一级缓存中的数据被保存到二级缓存中
- 新的会话查询信息,就从二级缓存中获取内容
- 不同的mapper查出的数据会放在自己对应的缓存(map)中
使用步骤
在mybatis的核心配置文件中,开启全局缓存
1
2<!-- 显式地开启全局缓存(默认已开启) -->
<setting name="cacheEnabled" value="true"/>在要使用二级缓存的Mapper中开启缓存
1
2
3
4
5
6
7
8<!-- 使用二级缓存 -->
<cache/>
<!-- 也可自定义参数 -->
<cache eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>如果readOnly参数没有设置,需要将实体类序列化(
implements Serializable
)。
获取数据顺序
- 先看二级缓存
- 再看一级缓存
- 查询数据库
自定义缓存 Ehcache
导包
1
2
3
4
5<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.3</version>
</dependency>在mapper中指定自定义的缓存实现
1
2<!-- 使用EhcacheCache -->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>搭配相应的配置文件
ehcache.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="./tmpdir/Tmp_EhCache"/>
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>
<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
但是目前都用Redis来做缓存,本章了解一下。
MyBatis-Plus
1 | <!-- mybatis plus --> |
代码生成器
1 | public class CodeGenerator { |
com.baomidou.mybatisplus.extension.service.IService#save
com.baomidou.mybatisplus.extension.service.IService#getOne(com.baomidou.mybatisplus.core.conditions.Wrapper
)
mybatis plus做了封装,插入数据自动回传主键到对象。orderMapper.insert(order);