感谢狂神,讲解清晰,以下是原视频【狂神说Java】Mybatis最新完整教程IDEA版通俗易懂_哔哩哔哩_bilibili的笔记
环境:
- JDK 1.8
- Mysql 8.0+
- maven 3.6.1
- IDEA
回顾:
- JDBC
- Mysql
- Java基础
- Maven
- Junit
SSM框架:配置文件的。(看官网文档);
简介
什么是Mybatis
- MyBatis 是一款优秀的持久层框架
- 它支持自定义 SQL、存储过程以及高级映射。
- MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
- MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
- MyBatis本是apache的一个iBatis,2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis。2013年11月迁移到Github。
如何获得:
- Maven仓库
org.mybatis
mybatis
3.5.6
- Github:https://github.com/mybatis
- 中文文档:MyBatis中文网
持久化
数据持久化
- 持久化就是将程序的数据在瞬时状态和持久状态转化的过程
- 内存:断电即失
- 数据库(jdbc),io文件持久化
为什么需要持久化?
- 有的对象不能失去
- 内存贵
持久层
Dao层、Sevice层、Controller层...
- 完成持久化工作的代码块
- 层界限十分明显
为什么需要Mybatis?
- 把数据存到数据库中
- 方便
- 传统的JDBC代码太复杂。简化、框架、自动化
- 不用Mybatis也可以。
优点:
- 解除SQL和程序代码的耦合
- 提供映射标签,支持对象与数据库的orm字段关系映射
- 提供对象关系映射标签,支持对象关系组件维护
- 提供xml标签,支持编写动态sql
第一个Mybatis程序
思路:搭建环境->导入Mybatis->编写代码->测试。
搭建环境
搭建数据库
CREATE TABLE user (
id INT(20) NOT NULL,
name VARCHAR(30) DEFAULT NULL,
pwd VARCHAR(30) DEFAULT NULL,
PRIMARY KEY(id)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO USER (id, name, pwd) VALUES
(1, '狂神', '123456'),
(2, '张三', '123456'),
(3, '李四', '123890')
新建项目
- 新建一个普通的maven项目
- 删除src目录
- 导入maven依赖(版本自定)
mysql
mysql-connector-java
8.0.22
org.mybatis
mybatis
3.5.6
junit
junit
4.13.2
test
编写mybatis工具类:
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
String resource = "mybatis-config.xml";
// InputStream inputStream = null;
try (InputStream inputStream = Resources.getResourceAsStream(resource);){
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
// 通过factory获得sqlsession
public static SqlSession getSqlSession() {
SqlSession sqlSession = sqlSessionFactory.openSession();
return sqlSession;
}
}
目录结构:
mybatis配置、用户、用户Dao以及用户Mapper:mybatis-config.xml、User、UserDao、UserMapper.xml
其中,serverTimezone是针对MySQL8的设置。
public class User {
private int id;
private String name;
private String pwd;
public User() {
}
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 + '\'' + '}';
}
}
public interface UserDao {
List getUserList();
}
然后编写一个测试类:
public class UserDaoTest {
@Test
public void test() {
// 得到Session
SqlSession sqlSession = MybatisUtils.getSqlSession();
// form-1:getMapper
UserDao mapper = sqlSession.getMapper(UserDao.class);
List userList = mapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
}
注意1:此时,会报出找不到xml文件的错误,主要是由于maven导致的问题,应该在pom.xml的配置文件下添加如下语句:
src/main/resources
**/*.properties
**/*.xml
true
src/main/java
**/*.properties
**/*.xml
true
注意2:如果出现“ UTF-8 序列的字节 2 无效。”是因为项目的默认配置编码不是UTF-8。解决方法是:将IDEA设置为UTF-8编码。或者将中文注释的字符去掉。
最终输出为:
User{id=1, name='狂神', pwd='123456'}
User{id=2, name='张三', pwd='123456'}
User{id=3, name='李四', pwd='123890'}
总结:编写Mybatis工具类->写Mybatis配置->写实体类->写实体类Mapper
问题:1、注释带中文字符可能会报错;2、Maven约定大于配置,可能存在找不到配置文件的情况,此时要对Maven进行配置;3、mysql8和mysql5配置存在差异,体现在mybatis的xml配置文件中。
继续深入其他语句(CRUD)
Namespace要对应到Dao;
Select选择查询语句
- id:对应namespace的方法名
- resultType:sql返回值
- parameterType:参数类型
- Insert
insert into mybatis.user (id, name, pwd) values (#{id}, #{name}, #{pwd});
- update
update mybatis.user set name=#{name}, pwd=#{pwd} where id = #{id};
- delete
delete from mybatis.user where id = #{id};
总结:编写接口->编写对应的mapper里的sql语句->测试(增删改需要提交事务)
- 万能Map
insert into mybatis.user (id, name, pwd) values (#{userId}, #{userName}, #{passWord});
- 模糊查询
总结:
- map传递使用参数为Key名
- Object传递使用参数为属性名
- 单个基本类型参数可以直接在sql取到
- 多个参数用map或者注解
- 通配符尽可能固定,需要考虑到注入问题。
配置解析
1、 核心配置文件
- mybatis-config.xml
- MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。
- configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- environment(环境变量)
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)
重点在:properties、environments、mappers等。
- transactionManager默认为JDBC
- dataSource默认为POOLED
- 学会如何配置多套environment
- properties可以动态替换(使用外部的配置文件比如"db.properties")
- typeAliases可以给实体类起别名(可以指定包名,找到对应的Beans)
- Settings里面"logImpl",“useGeneratedKeys”,“mapUnderscoreToCamelCase”等需要了解。
- Others
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- MyBatis-generator-core
- MyBatis-plus
- 通用Mapper
配置文件里面可以使用properties来引入外部文件里的属性:(配置的位置需要在Configuration里面的最开始,不然会报错)
db.properties的内容(即属性)。(原本的&用&代替,不然会报错)
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF8&serverTimezone=UTC
username=你自己的用户名
password=你自己的密码
typeAliases(别名)的使用
直接指定别名:(实体类少时,推荐使用)
指定包名让MyBatis自己去找对应的:(实体类多时,推荐使用)
或者在User里面使用注解:
@Alias("User")
public class User
就可以使用User来代替之前的com.pojo.User
Mappers
用class来配置mapper时,接口和配置文件要同名且在同一个包下,用package配置同理。以下是错误示范:
解决:应该将UserDao重命名为UserMapper。
最终,可以通过三种方式来配置,任选其一都可以测试成功:
作用域和生命周期
- 一旦创建了 SqlSessionFactory,就不再需要SqlSessionFactoryBuilder了。(只用一次)
- SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。(单例)
- 每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。(用完关闭,如下所示:)
try (SqlSession session = sqlSessionFactory.openSession()) {
// 你的应用逻辑代码
}
ResultMap(解决对象属性名和数据表字段名不一致的问题)详见:XML 映射器_MyBatis中文网
比如User此时为:
public class User {
private int id;
private String name;
private String password;
属性password与数据库中pwd不同名,此时查询出来的结果如下。
User{id=1, name='狂神', password='null'}
User{id=2, name='张三', password='null'}
User{id=3, name='李四', password='null'}
可见,丢失了最后一个字段,有两种解决方法。
别名(修改查询语句):
select id, name, pwd as password from mybatis.user
ResultMap(对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。):设定一个UserMap的resultMap,类型为User。然后,指定方法的resultMap为UserMap,即可完成映射。
总结:
- ResultMap 元素是 MyBatis 中最重要最强大的元素。
- ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
- ResultMap 的优秀之处——你完全可以不用显式地配置它们。
日志
日志工厂
异常排错:sout、debug、日志工厂
使用logImpl,指定 MyBatis 所用日志的具体实现,未指定时将自动查找。可选为:SLF4J(掌握?) | LOG4J(出Bug,掌握) | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING(掌握) | NO_LOGGING。默认无。
对mybatis-config.xml进行配置,注意settings所在的顺序位置。比如下面的命令行LOG。
Log4j
是什么(引自百度百科):Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等。我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,通过一个配置文件来灵活地进行配置,精细控制日志的生成过程,而不改代码。
怎么用:
- 先导包:
log4j
log4j
1.2.17
- 再配置:resources/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
- 再把settings改成使用LOG4J:
- 使用:
static Logger logger = Logger.getLogger(userDaoTest.class); // 当前类
@Test
public void testLog4j(){ //多种日志的级别
logger.info("info:进入了testlog4j");
logger.debug("debug:进入了testlog4j");
logger.warn("warn:进入了testlog4j");
logger.error("error:进入了testlog4j");
}
- 输出:
[dao.userDaoTest]-info:进入了testlog4j
[dao.userDaoTest]-debug:进入了testlog4j
[dao.userDaoTest]-warn:进入了testlog4j
[dao.userDaoTest]-error:进入了testlog4j
分页
目的:减少运算数据量。
1、 LIMIT实现分页
实现sql的相关接口。
List getUserByLimit(Map map);
进行测试。
@Test
public void testGetUserByLimit(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap parameterMap = new HashMap<>();
parameterMap.put("startIndex", 1);
parameterMap.put("endIndex", 2);
List userByLimit = mapper.getUserByLimit(parameterMap);
for (User user: userByLimit) {
System.out.println(user);
}
sqlSession.close();
}
2、RowBounds实现分页
List getUserByRowBounds();
@Test
public void testGetUserByRowBounds() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
// Java层面来直接selectList
RowBounds rowBounds = new RowBounds(1, 2);
List userList = sqlSession.selectList("com.dao.UserMapper.getUserByRowBounds", null, rowBounds);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
3、分页插件
PageHelper等。
使用注解开发
面向接口编程
目的:解耦
- 个体的抽象:abstract class
- 个体的某个方面的抽象:interface
三个面向
- 面向对象:考虑对象的属性和方法。
- 面向过程:以流程(事务)考虑实现。
- 接口设计与非接口设计:与另外两个考虑的不是一个问题,更多的是对系统整体的架构。
注解开发
可以不配置对应mapper的xml,使用简单的注解。
@Select("select * from user")
List getUsers();
然后,绑定接口:
@Test
public void testGetUsers() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List users = mapper.getUsers();
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
- 实现:反射
- 底层:动态代理
MyBatis执行流程:
加载配置->SqlSessionFactoryBuilder(构建XMLConfigBuilder->转换Configuration)->SqlSessionFactory->(transaction事务管理->executor->sqlSession->CRUD->是否执行成功)->提交事务->关闭
继续CRUD
增改删(注解版)及测试:
@Insert("insert into user(id, name, pwd) values (#{id},#{name},#{password})")
int addUser(User user);
@Update("update user set name=#{name}, pwd=#{password} where id = #{id}")
int updateUser(User user);
@Delete("delete from user where id = #{id}")
int deleteUser(int id);
@Test
public void testAddUser() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int res = mapper.addUser(new User(6, "aaa", "1234555"));
if (res > 0){
System.out.println("插入成功");
}
sqlSession.commit();
sqlSession.close();
}
@Test
public void testUpdateUser() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int res = mapper.updateUser(new User(6, "aaa", "6666"));
if (res > 0){
System.out.println("改变成功");
}
sqlSession.commit();
sqlSession.close();
}
@Test
public void testDeleteUser() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int res = mapper.deleteUser(6);
if (res > 0){
System.out.println("删除成功");
}
sqlSession.commit();
sqlSession.close();
}
关于@Param注解
- String或者基本类的需要加上
- 引用类型不需要加
- 只有一个基本类型可以忽略
- SQL中引用的就是@Param中的属性名
#{}和${}
- $直接拼接,无法防止注入
- $多用在传入数据库参数时
- 尽量使用#
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.
- 通过IDEA进行插件安装
- 导包
org.projectlombok
lombok
1.16.16
provided
Lombok注解内容:
@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
@val
@var
experimental @var
@UtilityClass
Lombok config system
Code inspections
Refactoring actions (lombok and delombok)
- @Data:无参构造,get,set,toString,hashcode,equals
- @AllArgsConstructor:有参构造
- @NoArgsConstructor:无参构造
- @Getter
- @Setter
- @ToString
复杂查询环境
- 多个学生对应/关联一个老师(多对一)
- 集合,一个老师有很多学生(一对多)
先建表搭建环境:
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` (`id`, `name`, `tid`) VALUES ('1', '小明', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');
流程:导入lombok->建立实体类->建立Mapper接口->建立Mapper.xml文件->在核心配置文件中绑定Mapper->测试查询是否成功。
多对一
按照查询嵌套处理
查找学生的时候,通过外键查询对应的老师。
按照结果嵌套处理
一对多
按照结果嵌套处理
按照查询嵌套处理
总结:
- 保证SQL可读性
- 一对多和多对一的字段和属性名的对应问题
- 问题如果不好排查,使用日志系统,如:Log4j
- 最终SQL优化的方向为:学习和理解Mysql引擎、InnoDB底层原理、索引、索引优化!
动态SQL
是什么:根据不同条件生成SQL语句。
搭建环境:
CREATE TABLE `blog`(
`id` VARCHAR(50) NOT NULL COMMENT '博客id',
`title` VARCHAR(100) NOT NULL COMMENT '博客标题',
`author` VARCHAR(30) NOT NULL COMMENT '博客作者',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`views` INT(30) NOT NULL COMMENT '浏览量'
)ENGINE=INNODB DEFAULT CHARSET=utf8;
@Test
public void testAddBlogs() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Blog blog = new Blog();
blog.setId(IDutils.getID());
blog.setAuthor("KyoDante");
blog.setCreateTime(Timestamp.from(Instant.now()));
blog.setTitle("Mybatis如此简单");
blog.setViews(7777);
mapper.addBlog(blog);
blog.setId(IDutils.getID());
blog.setTitle("Spring如此简单");
blog.setCreateTime(Timestamp.from(Instant.now()));
mapper.addBlog(blog);
blog.setId(IDutils.getID());
blog.setTitle("Java如此简单");
blog.setCreateTime(Timestamp.from(Instant.now()));
mapper.addBlog(blog);
blog.setId(IDutils.getID());
blog.setTitle("微服务如此简单");
blog.setCreateTime(Timestamp.from(Instant.now()));
mapper.addBlog(blog);
sqlSession.commit();
sqlSession.close();
}
public class IDutils {
public static String getID() {
return UUID.randomUUID().toString().replace("-", "");
}
}
IF
常用标签
where和if搭配(只要满足就填入SQL)
Choose(类似Switch,多选一)
Trim
与 set 元素等价的自定义 trim 元素:
...
sql和include
- 用sql抽取复用的部分
and title = #{title}
and author = #{author}
- 用include将该片段包括进去
FOREACH
缓存
是什么:查询连接数据库,耗资源,存到某些地方,下次可以直接使用!内存->缓存(解决高并发性能问题)
为什么:减少和数据库的交互开销,提高系统效率
哪里使用:经常查询且不经常改变的数据。
- Mybatis有一级缓存和二级缓存
- 默认为开启一级缓存。(SqlSession级别的缓存;也称为本地缓存)
- 二级缓存是手动开启和配置,基于namespace级别的缓存
- 为了提高扩展性,定义了缓存接口Cache,可以实现它来定义二级缓存
一级缓存
@Test
public void test() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println("==================");
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user);
System.out.println(user == user2);
sqlSession.close();
}
- 增删改会刷新缓存
@Test
public void test() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
User user3 = new User();
user3.setId(2);
user3.setName("aaaa");
user3.setPwd("bbbb");
int res = mapper.updateUserById(user3);
if (res > 0) {
System.out.println("改变成功");
}
System.out.println("==================");
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user);
System.out.println(user == user2);
sqlSession.close();
}
- 或者清除缓存
sqlSession.clearCache();
二级缓存
为了什么:一级缓存只在SqlSession作用域内,会话没了,还会存在于二级缓存中。新的会话可以从二级缓存中获得内容。不同mapper放在对应的map中。
在Mapper中设置(如果直接这么设置,需要序列化实体类,即implements Serializable)
在核心配置中使用(默认开启):
- 最开始是一级缓存
- 只有在关闭会话,或者提交会话的时候,才会转到二级缓存
缓存原理
提高查询效率:
从用户的角度:先查二级再查一级。
从程序的角度:先从SqlSession,也即一级缓存;再到Mapper,也即二级缓存。
EhCache
- 导包
org.mybatis.caches
mybatis-ehcache
1.2.1
- 配置resources/ehcache.xml
使用
Redis:K-V来实现缓存。(现在常用)