文章目录
- 一、介绍
- 什么是持久层
- 为什么要学MyBatis?
- 二、如何获得MyBatis?
- 三、第一个Mybatis程序
- 数据库
- 导入maven依赖
- bean 实体类
- dao持久层
- resources
- 编写对应的映射文件
- mybatis主配置文件
- 测试类
- 运行遇到报错Could not find resource com/qibu/dao/IUserDao.xml
- 使用注解的方式
- 查看日志
- 1. 指定日志代理工具是LOG4J
- 2. 引入LOG4J工具包
- 3. 设置配置文件
- 4. 运行
- log4j没效果
- 只要一设置代理就报错The setting logImpl is not known. Make sure you spelled it correctly (case sensitive).
- 通过id查找用户
- 增(添加一个用户)
- \<selectKey>
- 改(修改用户信息)
- 删(删除一个用户)
- 模糊查询
- #{} 和 ${} 的区别
- 查询总记录数
- 当数据库名和类的属性名不匹配时
- resultMap标签
- id标签
- result标签
- 给实体类设置别名
- 批量定义\<package>
- 批量别名定义
- 批量扫描映射文件
- 四、使用传统的dao层开发(了解即可)
- 五、mybatis连接池
- 六、动态sql语句
- \<if>标签
- \<where>标签
- \<sql>标签和\<include>
- \<foreach>
- 七、多表查询
- 数据库
- 多对一
- 第一种方式
- 实体类
- dao
- 映射文件
- 测试类
- 第二种方式(常用)
- 实体类
- dao
- 映射文件(association 一对一关联)
- 测试类
- 一对多
- 实体类
- dao
- 映射文件(collection 一对多关系)
- 测试类
- 多对多
- 数据库表
- 多对多操作-查询角色获取角色下所属用户信息
- bean(实体类)
- dao
- 映射文件
- 测试类
- 多对多操作-查询用户获取用户所包含的角色信息
- bean(实体类)
- dao
- 映射文件
- 测试类
- java.io.IOException: Could not find resource mybatis-config.xml
- java: 程序包com.qibu.dao不存在(xxx程序包不存在)
- 八、缓存
- 一级缓存
- 二级缓存
- 开启二级缓存
- 配置相应的sql映射文件(xxx.xml)
- Bean实现Serializable接口
- 写测试类
- 九、mybatis注解开发
- mybatis的常用注解说明
- 数据库
- bean实体类
- dao
- 主配置文件
- 配置properties文件
- 测试类
- 报错SSL
- 缓存
- 一级缓存
- 二级缓存
- 使用注解配置二级缓存
一、介绍
Spring是一个开放源代码的J2EE应用程序框架,负责管理协调 控制层和持久层。两大特性:IoC(控制反转)和AOP(面向切面编程),它有几大组件,分别是 Spring Core、 Spring AOP、 Spring ORM、 Spring DAO、Spring Context、 Spring Web和 Spring Web MVC。
SpringMVC是一个基于mvc的框架,其实就是Spring其中一个组件。
SpringBoot简单来讲就是简化Spring应用的,不是对Spring功能上的增强,其实就是简化Spring环境的搭建以及开发的流程的。
SpringCloud是Spring给出的一套解决方案,被称为构建分布式微服务系统的“全家桶”,它并不是某一门技术,而是一系列微服务解决方案或框架的有序集合。
MyBatis 是一个基于Java的持久层
框架,负责与我们数据库进行打交道的一个框架,内部实际上是封装了jdbc,让我们的dao开发起来会更简单。它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。
MyBatis本是apache的一个开源项目iBatis
,2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis。2013年11月迁移到Github。
ORM思想
对象关系映射(Object Relational Mapping)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。
什么是持久层
主要就是做数据持久化
-
数据持久化
- 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
- 内存:断电即失
- 让数据持久化:数据库(jdbc)、io文件持久化。 为什么需要持久化
- 有一些对象,不能让他丢掉。
- 内存太贵了 持久层
- 完成持久化工作的代码块
- 层界限十分明显
为什么要学MyBatis?
jdbc的缺陷
- 要创建连接,释放资源对程序的资源造成了资源浪费。
- SQL语句在代码中编写,造成不容易维护。
- 对结果集解析,要把结果进行封装,很麻烦。
这些问题都被框架简化掉了
-
为什么要用MyBatis?
- 方便程序员将数据存到数据库中
- 帮助
- 传统的JDBC代码太复杂了,简化,框架,自动化。
- 不用MyBatis也可以,只是学了MyBatis更容易上手
-
优点:
- 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件。
- 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里。
- sql和代码的分离,提高了可维护性。
- 提供映射标签,支持对象与数据库的ORM字段关系映射。
- 提供对象关系映射标签,支持对象关系组建维护。
- 提供xml标签,支持编写动态sql。
二、如何获得MyBatis?
maven仓库:https://mvnrepository.com/artifact/org.mybatis/mybatis
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
GitHub:https://github.com/search?q=mybatis
中文文档:https://mybatis.org/mybatis-3/zh/index.html
三、第一个Mybatis程序
创建一个Maven工程
PS:设置本地仓库这里,由于我用的IDEA 是2020.1版本,本地仓库只能是默认的,不然就会报错找不到包。
数据库
数据库名叫mydb,里面有一张用户表user
CREATE TABLE `user` (
`id` int(11) NOT NULL,
`username` varchar(30) NOT NULL,
`password` varchar(30) NOT NULL,
`sex` char(2) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
ALTER TABLE `user`
ADD PRIMARY KEY (`id`);
ALTER TABLE `user`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
INSERT INTO `user` (`id`, `username`, `password`, `sex`) VALUES
(1, '小意', '123456', '女'),
(2, '清风', '1234567', '男'),
(3, '微泫', '123123', '男');
导入maven依赖
pom.xml , scope作用域记得删掉,junit-jupiter这里不用也可以删掉
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
bean 实体类
src.main.java.com.qibu.bean
User 类
package com.qibu.bean;
public class User {
private Integer id;
private String username;
private String password;
private String sex;
public User(){
}
public User(Integer id, String username, String password, String sex) {
this.id = id;
this.username = username;
this.password = password;
this.sex = sex;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", sex='" + sex + '\'' +
'}';
}
}
dao持久层
main.java.com.qibu.dao
它不需要写实现类,我们只需要写接口就行了,IUserDao
接口,I表示接口的意思。
package com.qibu.dao;
import com.qibu.bean.User;
import java.util.List;
public interface IUserDao {
/**
* 查询所有用户信息
*/
public List<User> findAllUser();
}
resources
编写对应的映射文件
我们接口最终肯定还是要实现它,怎么实现?不是搞一个类实现它,我们需要在这个main包下面新建一个文件夹resources
,这个项目跟普通的JavaWeb项目不一样,它要求的是你的所有的一些配置文件、配置信息就应该放到resources下面。我们在resources下新建一个包com.qibu.dao
,然后新建一个实现类,这个实现类,它不是一个类,它是一个file文件后缀是xml,新建IUserDao.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!--DTD约束,用来约束xml文档。规定xml文档中元素的名称,子元素的名称及顺序,元素的属性等等。-->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 上面不写就报错 -->
<!-- namespace命名空间可以任选命名,但最好要定义一定规则,便于后继的使用,
例如我这个实现的是IUserDao接口,所以我们必须写这个包的全路径名com.qibu.dao.IUserDao
包的全限定名 包名+类名
-->
<mapper namespace="com.qibu.dao.IUserDao">
<!-- 实现查询所有用户信息的方法 -->
<!-- id是实现方法的名字,resultType 返回的结果类型 -->
<select id="findAllUser" resultType="com.qibu.bean.User">
select * from user
</select>
</mapper>
mybatis主配置文件
写在resources下,我们新建一个文件叫mybatis-config.xml
(名字可以随便取),注意DTD约束跟上面写的IUserDao.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="mysql">
<environment id="mysql">
<!--事务管理类型主要有jdbc和managed,前者依赖于数据源获得的连接,后者依赖于容器 -->
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<!-- 如果数据库设置为UTF-8,则URL参数连接需要添加?useUnicode=true&characterEncoding=UTF-8,如下 -->
<property name="url"
value="jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="密码"/>
</dataSource>
</environment>
</environments>
<!-- 告知mybatis映射配置的位置 -->
<mappers>
<!-- resource数据源 -->
<mapper resource="com/qibu/dao/IUserDao.xml"/>
</mappers>
</configuration>
测试类
main.java.com.qibu.test
新建一个测试类MyBatisTest
,注意是import org.apache.ibatis.io.Resources;
package com.qibu.test;
import com.qibu.bean.User;
import com.qibu.dao.IUserDao;
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 org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class MyBatisTest {
@Test
public void testfindAllUser() throws IOException {
//读取mybatis主配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//创建SqlSessionFactory的构建者对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//通过构建者对象创建工厂对象
SqlSessionFactory sessionFactory= builder.build(is);
//获得session对象
SqlSession session = sessionFactory.openSession();
//使用session创建dao接口的代理对象
IUserDao dao = session.getMapper(IUserDao.class);
//使用代理对象来执行查询所有的方法
List<User> users = dao.findAllUser();
for (User user : users) {
System.out.println(user.toString());
}
//释放资源
session.close();
is.close();
}
}
PS:等到我们MyBatis全部学完之后,学完Spring之后你会发现这里面所有的代码所有的操作都不需要你做了,就是这上面的代码都不需要你做,你只需要一行代码 dao.findAllUser() 就可以查出来了,全部简化掉。
运行,结果:
运行遇到报错Could not find resource com/qibu/dao/IUserDao.xml
告诉你没有找到com/qibu/dao/IUserDao.xml
可能是IDEA的bug吧,我的IDEA版本是2020.1,我们在resources下创建目录的时候不能直接com.qibu.dao。应该新建com,新建qibu,新建dao。
上面用的是xml的方式,接下来是用注解的方式,会非常的简单
使用注解的方式
用注解IUserDao.xml文件都不用了,删掉IUserDao.xml文件
,com.qibu.dao的IUserDao写注解 @Select("select * from user")
<mapper class="com.qibu.dao.IUserDao"></mapper>
我们先用xml的方式,我们先撤回上面注释删掉的操作
查看日志
我们可以给mybatis框架添加日志管理的功能,mybatis内置有日志工厂,日志工厂默认交给以下其中一种工具做代理:
- SLF4J
- Apache Commons Logging
- Log4j 2
- Log4j
- JDK logging
具体选择哪个日志实现工具由MyBatis的内置日志工厂确定。它会使用最先找到的(按上文列举的顺序查找)
。 如果一个都未找到,日志功能就会被禁用。
1. 指定日志代理工具是LOG4J
可做可不做
mybatis-config.xml
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
2. 引入LOG4J工具包
pom.xml
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
3. 设置配置文件
设置配置文件,也就是它的输出位置
resources目录下放log4j.properties文件,这些都不用我们去写,复制粘贴拿来用就好了。
# 设置全局的日志输出级别是debug debug < info < warn < error < fatal
# 设置全局的输出源是stdout-输出到控制台
log4j.rootLogger=debug,stdout
# My logging configuration...
log4j.logger.cn.jbit.mybatisdemo=DEBUG
# 设置stdout输出源的相关信息
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p %d %C: %m%n
log4j.logger.org.apache.ibatis=DEBUG
## log4j.logger.org.apache.jdbc.SimpleDataSource=DEBUG
log4j.logger.org.apache.ibatis.jdbc.ScriptRunner=DEBUG
## log4j.logger.com.ibatis.sqlmap.engine.impl.SqlMapclientDelegate=DEBUG
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
4. 运行
运行测试类MyBatisTest
PooledDataSource forcefully closed/removed all connections. 数据连接池准备就绪了。
Opening JDBC Connection 已经打开了JDBC的连接。
Created connection 893504292.创建了一个连接对象。这就说明了其实mybatis内部是有一个连接池的。
Setting autocommit to false on JDBC Connection 我们设置自动提交为false 。
Total: 3 查到的结果集为3条
Resetting autocommit to true on JDBC Connection 设置自动提交为true,数据库增删改每次一定要去提交事务。
Closing JDBC Connection关闭了这个连接。
Returned connection 893504292 to pool.返回这个连接给连接池,编号是893504292 。
log4j没效果
可能是因为我创建resources是直接点
解决办法:Ctrl + Shift + Alt + S
或者 File -> Project Structure
将我们的resources标记取消然后重新标记,重启IDEA,运行就好了。
只要一设置代理就报错The setting logImpl is not known. Make sure you spelled it correctly (case sensitive).
报错信息:
org.apache.ibatis.exceptions.PersistenceException:
### Error building SqlSession.
### The error may exist in SQL Mapper Configuration
### Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: org.apache.ibatis.builder.BuilderException: The setting logImpl is not known. Make sure you spelled it correctly (case sensitive).
at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:23)
at org.apache.ibatis.session.SqlSessionFactoryBuilder.build(SqlSessionFactoryBuilder.java:79)
at org.apache.ibatis.session.SqlSessionFactoryBuilder.build(SqlSessionFactoryBuilder.java:63)
at com.qibu.test.MyBatisTest.init(MyBatisTest.java:33)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Caused by: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: org.apache.ibatis.builder.BuilderException: The setting logImpl is not known. Make sure you spelled it correctly (case sensitive).
at org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration(XMLConfigBuilder.java:105)
at org.apache.ibatis.builder.xml.XMLConfigBuilder.parse(XMLConfigBuilder.java:88)
at org.apache.ibatis.session.SqlSessionFactoryBuilder.build(SqlSessionFactoryBuilder.java:77)
... 25 more
Caused by: org.apache.ibatis.builder.BuilderException: The setting logImpl is not known. Make sure you spelled it correctly (case sensitive).
at org.apache.ibatis.builder.xml.XMLConfigBuilder.settingsElement(XMLConfigBuilder.java:192)
at org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration(XMLConfigBuilder.java:99)
... 27 more
java.lang.NullPointerException
at com.qibu.test.MyBatisTest.destory(MyBatisTest.java:43)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:33)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Process finished with exit code -1
MyBatis 的版本换成3.2.7或以上,此问题解决,原因是MyBatis 3.1.1 -jar 还没有 logImpl 这个设置。
通过id查找用户
IUserDao
package com.qibu.dao;
import com.qibu.bean.User;
//import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface IUserDao {
/**
* 查询所有用户信息
*/
// @Select("select * from user")
public List<User> findAllUser();
/**
* 通过id来查询用户信息
*/
public User findById(Integer userId);
}
parameterType 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
resources.com.qibu.dao.IUserDao.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="com.qibu.dao.IUserDao">
<!-- 实现查询所有用户信息的方法 -->
<!-- id是实现方法的名字,resultType 返回的结果类型 -->
<select id="findAllUser" resultType="com.qibu.bean.User">
select * from user
</select>
<!-- 通过id查询用户 -->
<select id="findById" resultType="com.qibu.bean.User" parameterType="int">
select * from user where id=#{userid}
</select>
</mapper>
resultType是用来指定结果集的类型,支持基本数据类型和实体类类型
parameterType 用于指定参数类型(基本数据类型int或者它的包装类Integer)
#{}相当于占位符 ,相当于原来JDBC部分所学的?
#{userId} 具体的数据是由#{}的内容决定的,写什么名字都不是重点
由于parameterType
指定的数据类型是基本数据类型
,所以#{}
此处可以随便填写
main.java.com.qibu.test.MyBatisTest
改造了一下
package com.qibu.test;
import com.qibu.bean.User;
import com.qibu.dao.IUserDao;
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 org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class MyBatisTest {
private InputStream is;
private SqlSessionFactoryBuilder builder;
private SqlSessionFactory sessionFactory;
private SqlSession session;
private IUserDao dao;
@Before //在测试方法执行之前执行
public void init() throws IOException {
//读取mybatis主配置文件
is = Resources.getResourceAsStream("mybatis-config.xml");
//创建SqlSessionFactory的构建者对象
builder = new SqlSessionFactoryBuilder();
//通过构建者对象创建工厂对象
sessionFactory = builder.build(is);
//获得session对象
session = sessionFactory.openSession();
//使用session创建dao接口的代理对象
dao = session.getMapper(IUserDao.class);
}
@After //表示在测试方法执行之后执行
public void destory() throws IOException {
//提交事务 增删改后一定要控制事物的提交
session.commit();
//释放资源
session.close();
is.close();
}
@Test
public void testFindAllUser() {
//使用代理对象来执行查询所有的方法、
List<User> users = dao.findAllUser();
for (User user : users) {
System.out.println(user.toString());
}
}
@Test
public void testFindById() throws IOException {
User user = dao.findById(1);
System.out.println(user.toString());
}
}
@Before //在测试方法执行之前执行
@After //表示在测试方法执行之后执行
session.commit(); 提交事务。增删改之后一定要控制事务的提交,如果没有提交事务,那么增删改是没有效果的。
但是就有一个问题,如果你是查询你还提交事务干什么呢?这个后面学到Spring的时候,它会帮我们控制管理它,Spring在中间做管家,帮我们管理东西。
增(添加一个用户)
main.java.com.qibu.dao.IUserDao
/**
* 添加一个用户
* 返回受影响的行数
*/
public int saveUser(User user);
resources.com.qibu.dao.IUserDao.xml
<!-- 添加一个用户 -->
<insert id="saveUser" parameterType="com.qibu.bean.User">
insert into user(username,password,sex) values (#{username},#{password},#{sex})
</insert>
parameterType
属性代表的参数类型,因为我们要传入一个类的对象
,所以类型就要写类的全名称
#{}
里面的内容 由于保存方法的参数是User对象,此处内容要写User对象的属性名称
类的属性名称区分大小写
因为他遵循的是OGNL表达式
OGNL表达式(Object-Graph Navigation Language) 是apache提供的一种表达式语言, 对象图像导航语言
它会通过这一个属性username就可以找到User对象,然后找到User对象之后就可以找到User对象里面的这个username属性,找到这个属性username的时候,它就会去找这个属性username的get方法把值取出来,它就是这么去做的。
main.java.com.qibu.test.MyBatisTest
@Test
public void testSaveUser() throws IOException {
int result = dao.saveUser(new User(0, "意", "202020", "女"));
System.out.println(result + "行受影响");
}
结果:1行受影响
<selectKey>
很多时候,假如我们新增一个用户之后需要返回用户的id编号
配置保存时获取插入的id
select last_insert_id();得到刚新增进去记录的主键id值,只适用于自增主键
keyProperty 将查询到主键值设置到指定的对象的哪个属性
resultType 指定select last_insert_id();查询的结果类型
keyColumn数据库里面的哪一个字段
resources.com.qibu.dao.IUserDao.xml
<insert id="saveUser" parameterType="com.qibu.bean.User">
<selectKey keyColumn="id" keyProperty="id" resultType="int">
select last_insert_id();
</selectKey>
insert into user(username,password,sex) values (#{username},#{password},#{sex})
</insert>
因为字段名字是一样的,可以省略keyColumn=“id”
<insert id="saveUser" parameterType="com.qibu.bean.User">
<selectKey keyProperty="id" resultType="int">
select last_insert_id();
</selectKey>
insert into user(username,password,sex) values (#{username},#{password},#{sex})
</insert>
main.java.com.qibu.test.MyBatisTest
@Test
public void testSaveUser() throws IOException {
User user = new User();
user.setUsername("xy");
user.setPassword("123456");
user.setSex("男");
dao.saveUser(user);
System.out.println("插入数据的主键: "+user.getId());
}
运行
改(修改用户信息)
main.java.com.qibu.dao.IUserDao
/* 修改用户信息 */
public int updateUser(User user);
resources.com.qibu.dao.IUserDao.xml
<update id="updateUser" parameterType="com.qibu.bean.User">
update user set username=#{username},password=#{password},sex=#{sex} where id=#{id}
</update>
main.java.com.qibu.test.MyBatisTest
@Test
public void testUpdateUser() throws IOException {
User user = dao.findById(2);
user.setUsername("玉米排骨汤");
user.setPassword("666666");
user.setSex("女");
int result = dao.updateUser(user);
System.out.println(result + "行受影响");
}
运行testUpdateUser()
结果:1行受影响
删(删除一个用户)
main.java.com.qibu.dao.IUserDao
/**
* 删除一个用户
* @param id
* @return
*/
public int deleteUser(Integer id);
resources.com.qibu.dao.IUserDao.xml
<delete id="deleteUser" parameterType="java.lang.Integer">
delete from user where id=#{id}
</delete>
main.java.com.qibu.test.MyBatisTest
@Test
public void testDeleteUser() throws IOException {
int result = dao.deleteUser(12);
System.out.println(result + "行受影响");
}
运行testDeleteUser()
结果:1行受影响
模糊查询
main.java.com.qibu.dao.IUserDao
/**
* 模糊查询
* @param username
* @return
*/
public List<User> findByUserName(String username);
resources.com.qibu.dao.IUserDao.xml
<select id="findByUserName" parameterType="String" resultType="com.qibu.bean.User">
select * from user where username like #{username}
</select>
main.java.com.qibu.test.MyBatisTest
@Test
public void testFindByUserName() {
//使用代理对象来执行查询所有的方法、
List<User> users = dao.findByUserName("%排骨汤%");
for (User user : users) {
System.out.println(user.toString());
}
}
运行testFindByUserName()
这里还可以另一种写法
<select id="findByUserName" parameterType="String" resultType="com.qibu.bean.User">
select * from user where username like '%${value}%'
</select>
在原来的 #{} 占位符,改成了${value},注意如果是用模糊查询的写法,那么
${value}
的写法是固定的,不能写成其他名字
@Test
public void testFindByUserName() {
//使用代理对象来执行查询所有的方法、
List<User> users = dao.findByUserName("排骨汤");//注意这里不用%%
for (User user : users) {
System.out.println(user.toString());
}
}
#{} 和 ${} 的区别
- #{}表示一个占位符号 相当于 jdbc中的 ? 符号,#{} 可以实现向prepareStatement中的预处理语句中设置参数值,自动进行java类型和jdbc类型之间的转换
- #{} 可以防止sql注入(安全),${}方式无法防止sql注入
- #{}可以接收pojo类型(实体类型)或者基本数据类型,${} 将我们的参数内容不进行jdbc的转换
- 如果传递简单类型值,#{} 括号中可以是value或者其他名称。
- 通过${}可以将内容拼接在sql语句中且不进行类型转换,${}可以接受简单数据类型或者pojo属性值, 如果传递简单类型值,${}扩种只能是value
#{}方式能够很大程度防止sql注入(安全),${}方式无法防止sql注入
,在JDBC能使用占位符的地方,最好优先使用#{},在JDBC不支持使用占位符的地方,就只能使用${},典型情况就是 动态参数。
举个例子,比如 有两张表,分别是xxx_a 和 xxx_b .如果需要在查询语句中 动态指定表名,就只能使用${}
<select>
select * from xxx_${value}
<select>
再比如MyBatis 排序时使用order by 动态参数时,此时也只能使用${}
<select>
select * from user order by ${value}
</select>
查询总记录数
main.java.com.qibu.dao.IUserDao
/**
* 查询总记录数
* @return
*/
public int findTotal();
resources.com.qibu.dao.IUserDao.xml
<select id="findTotal" resultType="int">
select count(*) from user
</select>
main.java.com.qibu.test.MyBatisTest
@Test
public void testFindTotal() {
int total = dao.findTotal();
System.out.println("总记录数" + total);
}
当数据库名和类的属性名不匹配时
假如我数据库的字段名是这样的:
我们随便运行一个查询,返回回来的是null
当实体类的属性跟数据库里的字段名不一致的时候,没有办法对我们的结果进行自动化封装。解决办法:我们可以用resultMap 。
resultMap建立查询的列名和实体类属性名称不一致时建立对应关系,从而实现封装。
在select标签中使用resultMap属性指定引用即可。
同时resultMap属性可以实现查询结果映射为复杂类型的pojo。比如实现一对一或者一对多。
resources.com.qibu.dao.IUserDao.xml
建立User实体与数据的映射关系
<resultMap id="UserMap" type="com.qibu.bean.User">
<id property="id" column="user_id"></id>
<result property="username" column="user_name"></result>
<result property="password" column="user_password"></result>
<result property="sex" column="user_sex"></result>
</resultMap>
<select id="findAllUser" resultMap="UserMap">
select * from user
</select>
resultMap标签
id属性: 给一个唯一的标识,是给查询的select标签的resultMap属性来引用的。
type属性: 指定实体类的全类名。
resultMap 标签可以建立查询的列名和实体类的属性名称不一致时,建立对应关系,从而实现封装。
在select标签中使用resultMap属性指定引用即可。
同时resultMap属性可以实现查询结果映射为复杂类型的pojo。比如实现一对一或者一对多。
id标签
id标签用于指定主键字段,只有主键能用,其他的字段只能用result标签
property属性: 用于指定实体类属性名称
column属性: 用于指定数据库表中的列名
result标签
用于指定非主键字段
上面的是MyBatis基于Mapper(代理对象)的执行方式。还有另外一种方式,我们不用代理dao,使用传统的dao层开发,
了解即可
。
给实体类设置别名
我们每次在映射文件中,每次写实体类都要写很长一段,例如com.qibu.bean.User,这时候我们可以取一个别名。
type 实体类
alias 别名
mybatis-config.xml
<typeAliases>
<typeAlias type="com.qibu.bean.User" alias="User"></typeAlias>
</typeAliases>
这样参数就可以直接用别名了
IUserDao.xml
PS: 自己运行MyBatisTest文件测试就好了
批量定义<package>
有一个实体类就有一个 dao 一个映射文件xxx.xml,假如我们有50个实体类,我们要写50个 <mapper resource=“xxx.xml”/> 50个<typeAlias>吗?他有一个批量定义。
<package name=“父文件夹位置”/>
批量别名定义
mybatis-config.xml
<typeAliases>
<package name="com.qibu.bean"/>
</typeAliases>
批量别名定义,扫描整个包下的所有的类,别名为类名,并且使用别名的时候,首字母大小写都可以,不敏感。
批量扫描映射文件
mybatis-config.xml
<mappers>
<!-- 第一种,设置相对路径 resource数据源 -->
<!-- <mapper resource="com/qibu/dao/IUserDao.xml"/> -->
<!-- 第二种,使用 mapper 接口类路径-->
<!-- <mapper class="com.qibu.dao.IUserDao"></mapper> -->
<!--第三种,批量扫描-->
<package name="com.qibu.dao"></package>
</mappers>
四、使用传统的dao层开发(了解即可)
前面讲的 dao = session.getMapper(IUserDao.class); getMapper是代理Dao的开发方式,接口代理的开发方式, 现在的话主流的就是这种方式。
注意:保存的位置(Location)不是mybatis_day01目录下。
新建完之后又要搭框架,复制mybatis_day01的代码,dao目录下新建一个impl目录,新建UserDaoImpl实现类去实现这个接口。
package com.qibu.dao;
import com.qibu.bean.User;
import java.util.List;
public interface IUserDao {
public List<User> findAllUser();
public User findById(Integer userId);
public int saveUser(User user);
public int updateUser(User user);
public int deleteUser(Integer id);
}
PS:这里就只写几个方法吧,不全部复制了
首先我们要拿到一个SqlSessionFactory,我们要通过构造函数去获得SqlSessionFactory,因为我们要拿到SqlSessionFactory之后我们才可以去获得session,然后才可以调用里面的方法。再Session.selectList(),这跟我们之前的方式不一样,这个是调用Session里面的API方法,selectList意思就是查询所有,然后这里面它要定位到哪一个方法?com.qibu.dao.IUserDao.findAllUser这个方法。然后再 Session.close();这里需要关闭的,然后它查完之后就会自动的返回这个结果给我。
单个查询叫selectOne()
这种方式比较繁琐, 所以我们只是作为了解, 但是你要知道它有这几种方式的
package com.qibu.dao.impl;
import com.qibu.bean.User;
import com.qibu.dao.IUserDao;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import java.util.List;
public class UserDaoImpl implements IUserDao {
private SqlSessionFactory factory;
public UserDaoImpl(SqlSessionFactory factory){
this.factory=factory;
}
@Override
public List<User> findAllUser() {
SqlSession sqlSession = factory.openSession();
//查询所有
List<User> users = sqlSession.selectList("com.qibu.dao.IUserDao.findAllUser");//IUserDao的方法名findAllUser
sqlSession.close();
return users;
}
@Override
public User findById(Integer userId) {
SqlSession sqlSession = factory.openSession();
User user = sqlSession.selectOne("com.qibu.dao.IUserDao.findById", userId);
sqlSession.close();
return user;
}
@Override
public int saveUser(User user) {
SqlSession sqlSession = factory.openSession();
int result = sqlSession.insert("com.qibu.dao.IUserDao.saveUser", user);
sqlSession.commit();
sqlSession.close();
return result;
}
@Override
public int updateUser(User user) {
SqlSession sqlSession = factory.openSession();
int result = sqlSession.update("com.qibu.dao.IUserDao.updateUser", user);
sqlSession.commit();
sqlSession.close();
return result;
}
@Override
public int deleteUser(Integer id) {
SqlSession sqlSession = factory.openSession();
int result = sqlSession.delete("com.qibu.dao.IUserDao.deleteUser", id);
sqlSession.commit();
sqlSession.close();
return result;
}
}
IUserDao.xml代码一定要写对,这里直接把之前写的IUserDao.xml复制过来,测试类复制过来运行。
相当于在原来代理方式的基础上还要再写一个实现类,太麻烦了。
这才是一个完整的项目结构
五、mybatis连接池
mybatis 中也有连接池技术,但是它采用的是自己的连接池技术。
在我们配置数据源的时候,就实现了mybatis连接池配置了,mybatis-config.xml
-
Mybatis 将它自己的数据源(dataSource) 分为三类:
- POOLED 使用连接池的数据源(用的最多)
- UNPOOLED 不使用连接池的数据源
- JNDI 使用 JNDI 技术实现的数据源, 每个服务器对应的连接池技术都不一样,比如tomcat服务器使用的dpcp连接池。意思是我们这一个是使用 JNDI 技术实现数据库的连接池。
mybatis在初始化的时候,他会根据dataSource里面type属性来创建相应的数据类型的数据源,数据库连接池就是说当我们真正的要用的时候它才会去获取并创立连接打开这个连接,用完之后就会把这个连接归还给连接池,我们在前面的日志也看到了。
六、动态sql语句
根据用户信息查询用户列表,我也不知道是通过性别还是名字。
main.java.com.qibu.dao.IUserDao
/**
* 根据有用户信息,查询用户列表
* @param user
* @return
*/
public List<User> findByUser(User user);
<if>标签
test属性中写的是对象的属性名,使用的是OGNL表达式的写法
另外where 1=1 的作用是true
resources.com.qibu.dao.IUserDao.xml
<select id="findByUser" resultMap="UserMap" parameterType="User">
select * from user where 1=1
<if test="username!=null and username!=''">
and user_name like #{username}
</if>
<if test="sex!=null and sex!=''">
and user_sex like #{sex}
</if>
</select>
main.java.com.qibu.test.MyBatisTest
@Test
public void testFindByUser() throws IOException {
User user =new User();
user.setUsername("%泫%");
user.setSex("男");
List<User> users = dao.findByUser(user);
for (User user2 : users) {
System.out.println(user2);
}
}
<where>标签
简化where 1=1
,if条件如果有就会去给他拼接一个where关键字
<select id="findByUser" resultMap="UserMap" parameterType="User">
select * from user
<where>
<if test="username!=null and username!=''">
and user_name like #{username}
</if>
<if test="sex!=null and sex!=''">
and user_sex like #{sex}
</if>
</where>
</select>
<sql>标签和<include>
<sql>用于封装sql语句,<include>来调用
<sql id="queryAll">
select * from user
</sql>
<select id="findAllUser" resultMap="UserMap">
<include refid="queryAll"/>
</select>
<foreach>
foreach 标签用于遍历集合
collection 属性 就是你你要循环的集合
open 属性 代表语句的开始部分
close 属性 代表结束的部分
item 属性 代表遍历集合的每个元素,叫啥名字无所谓
separator 属性 代表分隔符
假如我想找id为1、3、5的用户
main.java.com.qibu.bean下新建一个QueryVo类,目的是为了封装几个数据的
package com.qibu.bean;
import java.util.List;
//目的是为了封装几个数据的
public class QueryVo {
private List<Integer> ids;
public List<Integer> getIds() {
return ids;
}
public void setIds(List<Integer> ids) {
this.ids = ids;
}
}
main.java.com.qibu.dao.IUserDao
/**
* 根据id集合查询用户信息
* @param vo
* @return
*/
public List<User> findIds(QueryVo vo);
resources.com.qibu.dao.IUserDao.xml
select * from user WHERE user_id in ( 1 , 3 , 5 )
<select id="findIds" resultMap="UserMap" parameterType="QueryVo">
<include refid="queryAll"/>
<where>
<if test="ids!=null and ids.size>0 ">
<foreach collection="ids" open="user_id in (" close=")" item="id" separator=",">
#{id}
</foreach>
</if>
</where>
</select>
main.java.com.qibu.test.MyBatisTest
@Test
public void testFindIds() throws IOException {
QueryVo vo = new QueryVo();
List<Integer> ids = new ArrayList<>();
ids.add(1);
ids.add(3);
ids.add(5);
vo.setIds(ids);
List<User> users = dao.findIds(vo);
for (User user : users) {
System.out.println(user);
}
}
七、多表查询
数据库
新增一个课程表。
create table course
(
course_id int auto_increment,
course_name varchar(30) null,
user_id int null,
constraint course_pk
primary key (course_id)
)comment '课程表';
insert into course(course_name, user_id)
values ("英语课",1),
("高等数学",1),
("区块链开发基础",2),
("C语言程序设计",2);
-
多个选课记录对应一个用户
-
对于
选课记录
,关联,多个选课记录关联一个用户【多对一】 -
对于
用户
,集合,一个用户有多个选课记录【一对多】
多对一
实现查询选课记录时,也要关联查询账户所对应的用户信息
隐式内连接
select c.*, u.user_name,u.user_sex from course c, user u WHERE u.user_id = c.user_id;
显式内连接
select c.*, u.user_name,u.user_sex from course c left join user u on c.user_id = u.user_id;
第一种方式
另外的去扩展了一个pojo类(CourseUser)作为输出类型,然后让它(CourseUser)去继承了(Course),然后在sql语句里面查询了所有的查询结果集的所有字段,这种方式相对来说是比较简单的,我们在开发的时候这种方式用的也比较多。
实体类
新建Course课程记录类
package com.qibu.bean;
public class Course {
private Integer courseid;
private String coursename;
private Integer userid;
public Integer getCourseid() {
return courseid;
}
public void setCourseid(Integer courseid) {
this.courseid = courseid;
}
public String getCoursename() {
return coursename;
}
public void setCoursename(String coursename) {
this.coursename = coursename;
}
public Integer getUserid() {
return userid;
}
public void setUserid(Integer userid) {
this.userid = userid;
}
}
新建CourseUser类是用户表和课程表组合,同时包含用户信息以及课程信息
package com.qibu.bean;
//同时包含用户信息以及课程信息
public class CourseUser extends Course{
private String username;
private String usersex;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getUsersex() {
return usersex;
}
public void setUsersex(String usersex) {
this.usersex = usersex;
}
@Override
public String toString() {
return "课程号:"+this.getCourseid()+",课程名:"+this.getCoursename()+",用户名:"+this.getUsername();
}
}
dao
ICourseDao
package com.qibu.dao;
import com.qibu.bean.CourseUser;
import java.util.List;
public interface ICourseDao {
//查询所有课程,同时获得课程的信息以及用户名称和性别
List<CourseUser> findAll();
}
映射文件
ICourseDao.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="com.qibu.dao.ICourseDao">
<resultMap id="CourseUserMap" type="CourseUser">
<id property="courseid" column="course_id"></id>
<result property="coursename" column="course_name"></result>
<result property="username" column="user_name"></result>
<result property="usersex" column="user_sex"></result>
<result property="userid" column="user_id"></result>
</resultMap>
<select id="findAll" resultMap="CourseUserMap">
select c.*, u.user_name,u.user_sex from course c, user u WHERE u.user_id = c.user_id
</select>
</mapper>
测试类
MyBatisTest2
package com.qibu.test;
import com.qibu.bean.CourseUser;
import com.qibu.dao.ICourseDao;
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 org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class MyBatisTest2 {
private InputStream is;
private SqlSessionFactoryBuilder builder;
private SqlSessionFactory sessionFactory;
private SqlSession session;
private ICourseDao dao;
@Before //在测试方法执行之前执行
public void init() throws IOException {
//读取mybatis主配置文件
is = Resources.getResourceAsStream("mybatis-config.xml");
//创建SqlSessionFactory的构建者对象
builder = new SqlSessionFactoryBuilder();
//通过构建者对象创建工厂对象
sessionFactory = builder.build(is);
//获得session对象
session = sessionFactory.openSession();
//使用session创建dao接口的代理对象
dao = session.getMapper(ICourseDao.class);
}
@After //表示在测试方法执行之后执行
public void destory() throws IOException {
//提交事务
session.commit();
//释放资源
session.close();
is.close();
}
@Test
public void testFindAll(){
List<CourseUser> list = dao.findAll();
for (CourseUser courseUser : list) {
System.out.println(courseUser);
}
}
}
第二种方式(常用)
实体类
我们在Course类中加入User类对象作为Course类的一个属性
package com.qibu.bean;
public class Course {
private Integer courseid;
private String coursename;
private Integer userid;
// 在Course类中加入User类对象作为Course类的一个属性
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Integer getCourseid() {
return courseid;
}
public void setCourseid(Integer courseid) {
this.courseid = courseid;
}
public String getCoursename() {
return coursename;
}
public void setCoursename(String coursename) {
this.coursename = coursename;
}
public Integer getUserid() {
return userid;
}
public void setUserid(Integer userid) {
this.userid = userid;
}
@Override
public String toString() {
return "Course{" +
"courseid=" + courseid +
", coursename='" + coursename + '\'' +
", userid=" + userid +
", user=" + user +
'}';
}
}
新建User类
package com.qibu.bean;
public class User {
private Integer id;
private String username;
private String password;
private String sex;
public User() {
}
public User(Integer id, String username, String password, String sex) {
this.id = id;
this.username = username;
this.password = password;
this.sex = sex;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", sex='" + sex + '\'' +
'}';
}
}
dao
ICourseDao
//方式二
List<Course> findAllCourse();
映射文件(association 一对一关联)
跟方式一是同一条sql语句
resultMap建立关系
association标签
property 属性名字
javaType 类型
<resultMap id="CourseMap" type="Course">
<id property="courseid" column="course_id"></id>
<result property="coursename" column="course_name"></result>
<result property="userid" column="user_id"></result>
<!-- 一对一的配置 -->
<association property="user" javaType="User">
<id property="id" column="user_id"></id>
<result property="username" column="user_name"></result>
<result property="password" column="user_password"></result>
<result property="sex" column="user_sex"></result>
</association>
</resultMap>
<select id="findAllCourse" resultMap="CourseMap">
select c.*, u.user_name, u.user_sex
from course c,
user u
WHERE u.user_id = c.user_id
</select>
测试类
MyBatisTest2
@Test
public void testFindAllCourse(){
List<Course> list = dao.findAllCourse();
for (Course course : list) {
System.out.println(course);
}
}
一对多
实现查询用户信息的同时,以及用户的所有选课记录。
collection标签 用于建立一对多中集合属性的对应关系;
property=“” 关联查询的结果集存储在User对象的那个属性上;
ofType=“” 关联的结果集中的对象类型,List集合中的对象的类型,此处可以使用别名或者全类名。简单来说就是用于指定集合元素的数据类型。
select u.*,c.course_id,c.course_name from user u left join course c on c.user_id = u.user_id;
实体类
我们在User类中加入Course类对象集合作为User类的一个属性
package com.qibu.bean;
import java.util.List;
public class User {
private Integer id;
private String username;
private String password;
private String sex;
//查询用户信息同时关联所有选课记录
private List<Course> courses;
public User() {
}
public User(Integer id, String username, String password, String sex) {
this.id = id;
this.username = username;
this.password = password;
this.sex = sex;
}
public List<Course> getCourses() {
return courses;
}
public void setCourses(List<Course> courses) {
this.courses = courses;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", sex='" + sex + '\'' +
", courses='" + courses + '\'' +
'}';
}
}
Course 类
package com.qibu.bean;
public class Course {
private Integer courseid;
private String coursename;
private Integer userid;
// private User user;
//
// public User getUser() {
// return user;
// }
//
// public void setUser(User user) {
// this.user = user;
// }
public Integer getCourseid() {
return courseid;
}
public void setCourseid(Integer courseid) {
this.courseid = courseid;
}
public String getCoursename() {
return coursename;
}
public void setCoursename(String coursename) {
this.coursename = coursename;
}
public Integer getUserid() {
return userid;
}
public void setUserid(Integer userid) {
this.userid = userid;
}
@Override
public String toString() {
return "Course{" +
"courseid=" + courseid +
", coursename='" + coursename + '\'' +
", userid=" + userid +
// ", user=" + user +
'}';
}
}
dao
IUserDao
package com.qibu.dao;
import com.qibu.bean.User;
import java.util.List;
public interface IUserDao {
public List<User> findAllUser();
}
映射文件(collection 一对多关系)
IUserDao.xml
collection标签用于建立一对多中集合属性的对应关系
property=“courses” 关联查询的结果集存储在User对象的那个属性上
ofType=“Course” 关联的结果集中的对象类型 List集合中对象的类型,此处可以使用别名或者全类名
ofType用于指定集合元素的数据类型
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace 命名空间 包的全限定名 包名+类名 -->
<mapper namespace="com.qibu.dao.IUserDao">
<resultMap id="UserMap" type="User">
<id property="id" column="user_id"></id>
<result property="username" column="user_name"></result>
<result property="password" column="user_password"></result>
<result property="sex" column="user_sex"></result>
<!-- 配置一对多 -->
<collection property="courses" ofType="Course">
<id property="courseid" column="course_id"></id>
<result property="coursename" column="course_name"></result>
<result property="userid" column="user_id"></result>
</collection>
</resultMap>
<!-- 配置查询所有操作 -->
<select id="findAllUser" resultMap="UserMap">
select u.*,c.course_id,c.course_name from user u left join course c on c.user_id = u.user_id;
</select>
</mapper>
测试类
UserDaoTest
package com.qibu.test;
import com.qibu.bean.User;
import com.qibu.dao.IUserDao;
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 org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class UserDaoTest {
private InputStream is;
private SqlSessionFactoryBuilder builder;
private SqlSessionFactory sessionFactory;
private SqlSession session;
private IUserDao dao;
@Before //在测试方法执行之前执行
public void init() throws IOException {
//读取mybatis主配置文件
is = Resources.getResourceAsStream("mybatis-config.xml");
//创建SqlSessionFactory的构建者对象
builder = new SqlSessionFactoryBuilder();
//通过构建者对象创建工厂对象
sessionFactory = builder.build(is);
//获得session对象
session = sessionFactory.openSession();
//使用session创建dao接口的代理对象
dao = session.getMapper(IUserDao.class);
}
@After //表示在测试方法执行之后执行
public void destory() throws IOException {
//提交事务
session.commit();
//释放资源
session.close();
is.close();
}
@Test
public void testFindAll(){
List<User> users = dao.findAllUser();
User user =users.get(0);
System.out.println(user);
}
}
多对多
多对多其实就是两个一对多
在生活当中哪些属于多对多?比如说 老师和学生,老师可以上很多班的课,学生可以上很多老师的课;一个班级可以有多个老师去上课,老师可以去多个班级上课; 一个人有很多角色,一个角色有很多人。
为了方便好看,我们新建一个项目
,弄好结构,复制log4j.properties(要就要,不要就无所谓了)、mybatis-config.xml、pom.xml 该复制的复制
数据库表
# 角色
DROP TABLE IF EXISTS role;
create table role
(
role_id int auto_increment comment 'ID' primary key,
role_name varchar(20)
);
insert into role(role_name)
values ("Java架构师"),
("前端工程师"),
("运维工程师"),
("Java开发工程师");
# 用户
DROP TABLE IF EXISTS user;
create table user
(
user_id int auto_increment comment 'userID' primary key,
user_name varchar(20),
birthday datetime,
sex varchar(1),
address varchar(100)
);
insert into user(user_name, birthday, sex, address)
values ("杰克", "2001-07-21", "男", "长沙天心区"),
("晚风", "2002-01-11", "女", "长沙雨花区"),
("小梦", "2003-09-02", "女", "长沙嘉新区"),
("露思", "2000-07-01", "女", "长沙天心区"),
("麦克", "2001-05-03", "男", "长沙开福区"),
("小华", "2000-08-10", "男", "长沙岳麓区"),
("小李", "2002-06-13", "男", "长沙天心区"),
("龙龙", "1999-02-01", "男", "望城区");
# 角色用户中间表
DROP TABLE IF EXISTS user_role;
create table user_role
(
user_id int,
role_id int
);
insert into user_role
values (1, 1),
(1, 4),
(2, 1),
(2, 2),
(2, 3),
(3, 1),
(4, 1),
(5, 2),
(6, 2),
(7, 2),
(8, 1),
(8, 2);
select u.*, r.role_id, r.role_name
from role r
left join user_role ur on r.role_id = ur.role_id
left join user u on u.user_id = ur.user_id;
多对多操作-查询角色获取角色下所属用户信息
bean(实体类)
Role角色实体类
package com.qibu.bean;
import java.util.List;
//角色
public class Role {
private Integer roleId;
private String roleName;
//多对多的关系映射 一个角色可以赋予多个用户
private List<User> users;
public Role() {
}
public Role(Integer roleId, String roleName) {
this.roleId = roleId;
this.roleName = roleName;
}
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
public Integer getRoleId() {
return roleId;
}
public void setRoleId(Integer roleId) {
this.roleId = roleId;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
@Override
public String toString() {
return "Role{" +
"roleId=" + roleId +
", roleName='" + roleName + '\'' +
", users=" + users +
'}';
}
}
User用户实体类
package com.qibu.bean;
import java.util.List;
// 用户和角色 多对多
// 用户表
public class User {
/* 从用户去出发,一个用户可以有多个角色,所以用户到角色的关系也是一对多关系,
这样我们就可以认为我们把User和这个Role多对多关系,把它拆分成两个一对多的关系来实现。
然后我们是不是再把那个代码再重新写一遍就可以了.
*/
private Integer user_id;
private String user_name;
private String birthday;//搞个日期也行
private String sex;
private String address;
// 实现 User 到 Role 的一对多查询
// private List<Role> roles;
public User() {
}
// public User(Integer user_id, String user_name, String birthday, String sex, String address, List<Role> roles) {
// this.user_id = user_id;
// this.user_name = user_name;
// this.birthday = birthday;
// this.sex = sex;
// this.address = address;
// this.roles = roles;
// }
public Integer getUser_id() {
return user_id;
}
public void setUser_id(Integer user_id) {
this.user_id = user_id;
}
public String getUser_name() {
return user_name;
}
public void setUser_name(String user_name) {
this.user_name = user_name;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
// public List<Role> getRoles() {
// return roles;
// }
//
// public void setRoles(List<Role> roles) {
// this.roles = roles;
// }
@Override
public String toString() {
return "User{" +
"user_id=" + user_id +
", user_name='" + user_name + '\'' +
", birthday='" + birthday + '\'' +
", sex=" + sex +
", address='" + address + '\'' +
// ", roles=" + roles +
'}';
}
}
dao
IRoleDao
package com.qibu.dao;
import com.qibu.bean.Role;
import java.util.List;
public interface IRoleDao {
// 查询所有角色
public List<Role> findAll();
}
映射文件
IRoleDao.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace 命名空间 包的全限定名 包名+类名 -->
<mapper namespace="com.qibu.dao.IRoleDao">
<!-- 定义一个Role表的resultMap -->
<resultMap id="roleMap" type="Role">
<id column="role_id" property="roleId"></id>
<result column="role_name" property="roleName"></result>
<collection property="users" ofType="User">
<id column="user_id" property="user_id"></id>
<result column="user_name" property="user_name"></result>
<result column="birthday" property="birthday"></result>
<result column="sex" property="sex"></result>
<result column="address" property="address"></result>
</collection>
</resultMap>
<select id="findAll" resultMap="roleMap">
select u.*,r.role_id,r.role_name from role r left join user_role ur on r.role_id = ur.role_id
left join user u on u.user_id = ur.user_id;
</select>
</mapper>
测试类
package com.qibu.test;
import com.qibu.bean.Role;
import com.qibu.dao.IRoleDao;
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 org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class RoleDaoTest {
private InputStream is;
private SqlSessionFactoryBuilder builder;
private SqlSessionFactory sessionFactory;
private SqlSession session;
private IRoleDao dao;
@Before //在测试方法执行之前执行
public void init() throws IOException {
//读取mybatis主配置文件
is = Resources.getResourceAsStream("mybatis-config.xml");
//创建SqlSessionFactory的构建者对象
builder = new SqlSessionFactoryBuilder();
//通过构建者对象创建工厂对象
sessionFactory = builder.build(is);
//获得session对象
session = sessionFactory.openSession();
//使用session创建dao接口的代理对象
dao = session.getMapper(IRoleDao.class);
}
@After //表示在测试方法执行之后执行
public void destory() throws IOException {
//提交事务
session.commit();
//释放资源
session.close();
is.close();
}
@Test
public void testFindAll(){
List<Role> roles = dao.findAll();
for (Role role : roles){
System.out.println(role);
// System.out.println(role.getUsers());
}
}
}
多对多操作-查询用户获取用户所包含的角色信息
从用户去出发,一个用户可以有多个角色,所以用户到角色的关系也是一对多关系,这样我们就可以认为我们把User和这个Role多对多关系,把它拆分成两个一对多的关系来实现。然后我们是不是再把那个代码再重新写一遍就可以了。
bean(实体类)
User用户类
package com.qibu.bean;
import java.util.List;
// 用户和角色 多对多
// 用户表
public class User {
/* 从用户去出发,一个用户可以有多个角色,所以用户到角色的关系也是一对多关系,
这样我们就可以认为我们把User和这个Role多对多关系,把它拆分成两个一对多的关系来实现。
然后我们是不是再把那个代码再重新写一遍就可以了.
*/
private Integer user_id;
private String user_name;
private String birthday;//搞个日期也行
private String sex;
private String address;
// 实现 User 到 Role 的一对多查询
private List<Role> roles;
public User() {
}
public User(Integer user_id, String user_name, String birthday, String sex, String address, List<Role> roles) {
this.user_id = user_id;
this.user_name = user_name;
this.birthday = birthday;
this.sex = sex;
this.address = address;
this.roles = roles;
}
public Integer getUser_id() {
return user_id;
}
public void setUser_id(Integer user_id) {
this.user_id = user_id;
}
public String getUser_name() {
return user_name;
}
public void setUser_name(String user_name) {
this.user_name = user_name;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
@Override
public String toString() {
return "User{" +
"user_id=" + user_id +
", user_name='" + user_name + '\'' +
", birthday='" + birthday + '\'' +
", sex=" + sex +
", address='" + address + '\'' +
", roles=" + roles +
'}';
}
}
dao
IUserDao
package com.qibu.dao;
import com.qibu.bean.User;
import java.util.List;
public interface IUserDao {
// 查询所有用户
public List<User> findAll();
}
映射文件
IUserDao.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace 命名空间 包的全限定名 包名+类名 -->
<mapper namespace="com.qibu.dao.IUserDao">
<!-- 定义一个Role表的resultMap -->
<resultMap id="userMap" type="User">
<id column="user_id" property="user_id"></id>
<result column="user_name" property="user_name"></result>
<result column="birthday" property="birthday"></result>
<result column="sex" property="sex"></result>
<result column="address" property="address"></result>
<collection property="roles" ofType="Role">
<id column="role_id" property="roleId"></id>
<result column="role_name" property="roleName"></result>
</collection>
</resultMap>
<select id="findAll" resultMap="userMap">
select u.*,r.role_id,r.role_name from role r left join user_role ur on r.role_id = ur.role_id
left join user u on u.user_id = ur.user_id;
</select>
</mapper>
测试类
package com.qibu.test;
import com.qibu.bean.User;
import com.qibu.dao.IRoleDao;
import com.qibu.dao.IUserDao;
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 org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class UserDaoTest {
private InputStream is;
private SqlSessionFactoryBuilder builder;
private SqlSessionFactory sessionFactory;
private SqlSession session;
private IUserDao dao;
@Before //在测试方法执行之前执行
public void init() throws IOException {
//读取mybatis主配置文件
is = Resources.getResourceAsStream("mybatis-config.xml");
//创建SqlSessionFactory的构建者对象
builder = new SqlSessionFactoryBuilder();
//通过构建者对象创建工厂对象
sessionFactory = builder.build(is);
//获得session对象
session = sessionFactory.openSession();
//使用session创建dao接口的代理对象
dao = session.getMapper(IUserDao.class);
}
@After //表示在测试方法执行之后执行
public void destory() throws IOException {
//提交事务
session.commit();
//释放资源
session.close();
is.close();
}
@Test
public void testFindAll(){
List<User> users = dao.findAll();
for (User user : users){
System.out.println(user);
System.out.println(user.getRoles());
}
}
}
java.io.IOException: Could not find resource mybatis-config.xml
首先,因为target文件夹下没有生成mybatis-config.xml文件,所以执行代码就会报错,由于target下没有生成mybatis-config.xml文件,代码运行就会报错:Could not find resource mybatis-config.xml
百度了很多,最后发现pom.xml文件中多了一行代码,将这行删掉或者注释,刷新maven再重新运行即可。
原因分析:因为在项目里建了一个子模块,而原本的项目变成了父模块,父模块默认的打打包方式为<packaging>pom</packaging>,所以添加子模块之后pom.xml中多出了这一行代码。
<packaging></packaging>这个标签是什么意思呢?她是设置项目打包方式的一个标签。
maven中的packaging标签
项目的打包类型:pom、jar、war,packing默认是jar类型
-
<packaging>pom</packaging>父类型都为pom类型
-
<packaging>jar</packaging> 内部调用或者是作服务使用
-
<packaging>war</packaging> 需要部署的项目
而pom是最简单的打包类型。不像一个jar,sar,或者ear,它生成的构件只是它本身。没有代码需要测试或者编译,也没有资源需要处理。**也就是说使用pom打包方式就不会去处理xml文件。**原本是没有packaging这一行代码的,默认的打包方式就是jar,所以将这行代码删掉或者改为<packaging>jar</packaging>
都是可以的。
java: 程序包com.qibu.dao不存在(xxx程序包不存在)
maven包管理的clean方法
1、把项目的MavenProject打开
2、点击clean,再点击run,即可运行
**分析其原因:**之前点击了clean,再点击install,生成了错误的target,清除target之后重新生成就ok了
八、缓存
像我们很多这种持久层框架的话,他都有这个缓存策略,通过缓存策略来减少数据库的IO次数和查询次数,从而来提高我们数据库的效率。在mybatis中有两种缓存,一个叫一级缓存,一个叫二级缓存。
一级缓存
一级缓存就是处于我们SqlSession里面。
怎么去证明一级缓存呢?
我们打开练习一对多的那个案例里面来(随便哪个方法都可以),我们怎么去证明一级缓存呢?
一级缓存是存在我们SqlSession当中,也就是说只要有SqlSession就可以去拿到一级缓存
首先我们要看日志,你看这个地方我们已经发送了sql语句了,去查了一次了,查了一次然后去打印出来了,结果我们第二次再查的时候,发现一个问题没有,他直接打印出来了,他有没有去数据库里面查?没有。这是怎么回事嘞?这就是刚刚提到的,在我们的SqlSession对象他是相当于说我们在代码中发起了两次查询,但是他只是到数据库里面去查了一次,这就意味着我们mybatis他提供给我们的这一个一级缓存就是这个叫SqlSession他已经起效了,所以导致我们第二次再去查的时候他就没有到数据库去查,而是从缓存里面去查的,所以这就是我们的一级缓存。
一级缓存就是处于SqlSession范围之内,当我们去调用SqlSession的修改添加删除commit或者close之后,他就会把我们这些缓存给它清除掉。
记住一点就行了,如果SqlSession去执行了commit操作(增删改),清空SqlSession中的一级缓存,这样的话做的目的就是为了让缓存中的最新的数据避免 脏读 。
所以市面上所说的这种缓存技术其实都是这个原理,比如redis、cache,他都是这个原理,mybatis本身自己就具备了一级缓存,这就是缓存的概念。
就是说当我们第一次先去查,查完之后就相当于这个数据没有找到这个数据,那么他就会去数据库里面去查,查到之后怎么做,他就会把这个数据保存到缓存里面,当我第二次去查的时候怎么做,先到缓存里面去找,缓存里面如果找不到才会再去数据库里面去查。
如图所示,session.clearCache();清除缓存,清除之后就会查两次!!!
二级缓存
二级缓存是更高级的缓存,叫文件级别缓存。
开启二级缓存
<!-- 开启二级缓存支持 -->
<setting name="cacheEnabled" value="true"/>
需要在mybatis的主配置文件当中要来开启一个二级缓存
配置相应的sql映射文件(xxx.xml)
useCache=“true”,表示针对我这个方法你帮我去使用二级缓存,每次查询最新的数据你就可以把它设置为false(禁用二级缓存),true表示开启二级缓存。
<cache eviction="LRU" flushInterval="6000" readOnly="false" size="1024"></cache>
或者
<cache/>
这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会 导致冲突。
-
可用的收回策略有:
- LRU – 最近最少使用的:移除最长时间不被使用的对象。
- FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
- SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
- WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
- 默认的是 LRU。
-
flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒 形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
-
size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的 可用内存资源数目。默认值是 1024。
-
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓 存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存 会返回缓存对象的拷贝(通过序列化) 。这会慢一些,但是安全,因此默认是 false。
<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>
-
第一种情况:当sql语句为select语句的情况下:
- flushCache默认值是false,表示任何时候语句被调用,都不会去清空本地缓存和二级缓存。
- useCache默认值是true,表示会将本条语句的结果进行二级缓存。
- 如果没有去配置flushCache、useCache,那么默认是启用缓存的
-
第二种情况:当sql语句为insert、update、delete语句的情况下:
- flushCache默认值为true,表示任何时候语句被调用,都会导致本地缓存和二级缓存被清空。
- 在insert、update、delete语句没有useCache属性。
- flushCache 将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:false。
- useCache 将其设置为 true,将会导致本条语句的结果被二级缓存,默认值:对 select 元素为 true。
<select id="findAllUser" resultMap="UserMap" useCache="true">
Bean实现Serializable接口
注意
:刚刚我们说过,二级缓存是文件级别缓存,所以他会把我们查询出来的对象 进行序列化,写到你的一个文件当中,但是序列化的过程当中 ,他发现 你的类没有序列化,所以你在这里你要怎么做?implements Serializable
写测试类
@Test
public void testFindAll(){
SqlSession sqlSession1 = sessionFactory.openSession();
IUserDao IUserDao1= sqlSession1.getMapper(IUserDao.class);
List<User> users = IUserDao1.findAllUser();
User user =users.get(0);
System.out.println("第一次查询"+user);
// 查一遍把一级缓存给它关了
sqlSession1.close();//一级缓存消失
SqlSession sqlSession2 = sessionFactory.openSession();
IUserDao IUserDao2= sqlSession2.getMapper(IUserDao.class);
List<User> users2 = IUserDao2.findAllUser();
User user2 =users2.get(0);
System.out.println("第二次查询"+user2);
sqlSession2.close();
// 再查一遍又把一级缓存给它关了
/* 我们来看他会不会发起两次请求。
如果没有发起两次查询,也就意味着二级缓存生效了!!!*/
}
我们来看有没有生效,我把SqlSession一级缓存清空掉,我再查一次,如果我第二次没有重新再查就说明我的二级缓存已经在帮我进行缓存了。
第二次是哪里取的?二级缓存,因为一级缓存已经被我清掉了。
九、mybatis注解开发
为了方便好看,创建一个新项目
mybatis的常用注解说明
@Insert: 新增
@Update: 修改
@Delete: 删除
@Select:
@SelectKey
keyColumn 表示插入数据以后,要返回的内容在数据表中对应的字段名称
keyProperty 实体类中对应的属性名称
statement ="select last_insert_id()" 表示定义的查询的子语句
resultType 表示返回主键的类型,要传一个字节码对象Integer.class,不能int
before false表示在插入之前执行,由于主键自增,所以你要写成false
@Result: 实现结果集封装
id =true表示是一个主键
column 字段是哪一个,大小写不敏感
property 属性 区分大小写
@Results: 可以配合@Result一起使用, 封装多个结果集,你这么去理解 相当于你在xml定义了一个<resultMap></resultMap>标签
id 取名字
@ResultMap: 实现引入@Results定义的封装
@One: 一对一封装
@Many: 一对多结果集封装 ,多对多Many to Many,我们会把Many to Many看成双向的 一对多
@SelectProvider: 实现动态SQL映射
@CacheNameSpace: 注解实现二级缓存
数据库
新建一张表
# 用户表
DROP TABLE IF EXISTS user;
create table user
(
user_id int auto_increment comment 'userID' primary key,
user_name varchar(20),
birthday datetime,
sex varchar(1),
address varchar(100)
);
insert into user(user_name, birthday, sex, address)
values ("杰克", "2001-07-21", "男", "长沙天心区"),
("晚风", "2002-01-11", "女", "长沙雨花区"),
("小梦", "2003-09-02", "女", "长沙嘉新区"),
("露思", "2000-07-01", "女", "长沙天心区"),
("麦克", "2001-05-03", "男", "长沙开福区"),
("小华", "2000-08-10", "男", "长沙岳麓区"),
("小李", "2002-06-13", "男", "长沙天心区"),
("龙龙", "1999-02-01", "男", "望城区");
bean实体类
package com.qibu.bean;
import java.util.List;
// 用户表
public class User {
private Integer userId;//故意让属性和数据库表中的字段不一致
private String userName;
private String userBirthday;//搞个日期也行
private String userSex;
private String userAddress;
public User() {
}
public User(Integer userId, String userName, String userBirthday, String userSex, String userAddress) {
// 有参构造有没有都无所谓
this.userId = userId;
this.userName = userName;
this.userBirthday = userBirthday;
this.userSex = userSex;
this.userAddress = userAddress;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", userBirthday='" + userBirthday + '\'' +
", userSex='" + userSex + '\'' +
", userAddress='" + userAddress + '\'' +
'}';
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserBirthday() {
return userBirthday;
}
public void setUserBirthday(String userBirthday) {
this.userBirthday = userBirthday;
}
public String getUserSex() {
return userSex;
}
public void setUserSex(String userSex) {
this.userSex = userSex;
}
public String getUserAddress() {
return userAddress;
}
public void setUserAddress(String userAddress) {
this.userAddress = userAddress;
}
}
dao
package com.qibu.dao;
import com.qibu.bean.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface IUserDao {
// 查询所有用户
// 不需要写映射文件了
@Select("select * from user ")
@Results(id = "userMap", value = {
@Result(id = true, column="user_id", property="userId"),
@Result(column="user_name", property="userName"),
@Result(column="birthday", property="userBirthday"),
@Result(column="sex", property="userSex"),
@Result(column="address", property="userAddress")
})
public List<User> findAllUser();//可以省略public
@Select("select * from user where user_id = #{uid}")
@ResultMap("userMap")
User findUserByUId(Integer uId);
@Insert("insert into user(user_name,birthday,sex,address) values(#{userName},#{userBirthday},#{userSex},#{userAddress})")
@SelectKey(keyProperty = "userId", keyColumn = "user_id", resultType = Integer.class, before = false, statement = "select last_insert_id()")
int saveUser(User user);
@Update("update user set user_name=#{userName}, birthday=#{userBirthday}, sex=#{userSex}, address=#{userAddress} where user_id=#{userId}")
int updateUser(User user);
@Delete("delete from user where user_id=#{uid}")
int deleteUser(Integer uId);
@Select("select count(*) from user ")
int findTotal();
@Select("select * from user where user_name like #{userName} ")
@ResultMap("userMap")
List<User> findByName(String userName);
}
主配置文件
mybatis-config.xml
配置properties文件
这里面是有一个加载顺序的,你要先加载它再加载他
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置properties文件,有加载顺序的,不放前面就报错 -->
<properties resource="jdbc.properties"></properties>
<settings>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
<!-- 日志 -->
<setting name="logImpl" value="LOG4J"/>
</settings>
<!-- 不用了 -->
<!-- <!– 给实体类定义别名 –>-->
<!-- <typeAliases>-->
<!-- <package name="com.com.qibu.bean"/>-->
<!-- </typeAliases>-->
<!--可以设置多个运行环境,满足不同需要,例如 开发、测试、生产环境上有不同一配置 -->
<environments default="mysql">
<environment id="mysql">
<!--事务管理类型主要有jdbc和managed,前者依赖于数据源获得的连接,后者依赖于容器 -->
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<!-- 如果数据库设置为UTF-8,则URL参数连接需要添加?useUnicode=true&characterEncoding=UTF-8,如下 -->
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!-- <!–可以设置多个运行环境,满足不同需要,例如 开发、测试、生产环境上有不同一配置 –>-->
<!-- <environments default="mysql">-->
<!-- <environment id="mysql">-->
<!-- <!–事务管理类型主要有jdbc和managed,前者依赖于数据源获得的连接,后者依赖于容器 –>-->
<!-- <transactionManager type="JDBC"/>-->
<!-- <dataSource type="POOLED">-->
<!-- <property name="driver" value="com.mysql.jdbc.Driver"/>-->
<!-- <!– 如果数据库设置为UTF-8,则URL参数连接需要添加?useUnicode=true&characterEncoding=UTF-8,如下 –>-->
<!-- <property name="url"-->
<!-- value="jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=UTF-8"/>-->
<!-- <property name="username" value="root"/>-->
<!-- <property name="password" value="123456"/>-->
<!-- </dataSource>-->
<!-- </environment>-->
<!-- </environments>-->
<!-- 告知mybatis映射配置的位置 -->
<mappers>
<!-- 第一种,设置相对路径 resource数据源 -->
<!-- <mapper resource="com/qibu/dao/IUserDao.xml"/> -->
<!--第三种,批量扫描-->
<!-- <package name="com.qibu.dao"></package>-->
<!-- 第二种,使用 mapper 接口类路径-->
<!-- <mapper class="com.qibu.dao.IUserDao"></mapper> -->
<!-- 使用package标签指定dao接口所在的包的位置 -->
<package name="com.qibu.dao" ></package>
</mappers>
</configuration>
jdbc.properties
useSSL=true
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
jdbc.username=root
jdbc.password=123456
useSSL=false
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
jdbc.username=root
jdbc.password=123456
测试类
package com.qibu.test;
import com.qibu.bean.User;
import com.qibu.dao.IUserDao;
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 org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class UserDaoTest {
private InputStream is;
private SqlSessionFactoryBuilder builder;
private SqlSessionFactory sessionFactory;
private SqlSession session;
private IUserDao dao;
@Before //在测试方法执行之前执行
public void init() throws IOException {
//读取mybatis主配置文件
is = Resources.getResourceAsStream("mybatis-config.xml");
//创建SqlSessionFactory的构建者对象
builder = new SqlSessionFactoryBuilder();
//通过构建者对象创建工厂对象
sessionFactory = builder.build(is);
//获得session对象
session = sessionFactory.openSession();
//使用session创建dao接口的代理对象
dao = session.getMapper(IUserDao.class);
}
@After //表示在测试方法执行之后执行
public void destory() throws IOException {
//提交事务
session.commit();
//释放资源
session.close();
is.close();
}
@Test
public void testFindAll(){
List<User> users =dao.findAllUser();
for (User user : users) {
System.out.println(user.toString());
}
}
@Test
public void testfindUserByUId(){
User user = dao.findUserByUId(3);
System.out.println(user);
}
@Test
public void testSaveUser(){
User user = new User();
user.setUserName("晚意");
user.setUserBirthday("2004-03-1");
user.setUserSex("女");
user.setUserAddress("广州白云区");
dao.saveUser(user);
System.out.println("新增后的主键id=>" + user.getUserId());
}
@Test
public void testupdateUser(){
User user = dao.findUserByUId(6);
user.setUserName("晚风2");
user.setUserBirthday("1999-01-01");
user.setUserSex("女");
int result=dao.updateUser(user);
System.out.println("受影响的行数"+result);
System.out.println("修改之后查一下");
User user2 = dao.findUserByUId(6);
System.out.println(user2);
}
@Test
public void testdeleteUser(){
int result=dao.deleteUser(8);
System.out.println("删除: 受影响的行数"+result);
testFindAll();
}
@Test
public void testfindTotal(){
int result = dao.findTotal();
System.out.println("总共有"+result+"条");
}
@Test
public void testfindByName(){
List<User> users =dao.findByName("%晚%");
for (User user : users) {
System.out.println(user);
}
}
}
报错SSL
报错:不建议在没有服务器身份验证的情况下建立 SSL 连接。 根据MySQL 5.5.45+、5.6.26+ 和 5.7.6+ 要求,如果未设置显式选项,则必须默认建立 SSL 连接。 为了符合不使用 SSL 的现有应用程序,verifyServerCertificate 属性设置为“false”。您需要通过设置 useSSL=false 来显式禁用 SSL,或者设置 useSSL=true 并为服务器证书验证提供信任库。
最省事的方法是修改数据库连接信息,在链接之后加上“useSSL=false
”,直接禁用SSL连接方式。jdbc.properties文件
org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
The last packet successfully received from the server was 990 milliseconds ago. The last packet sent successfully to the server was 973 milliseconds ago.
### The error may exist in com/qibu/dao/IUserDao.java (best guess)
### The error may involve com.qibu.dao.IUserDao.findAllUser
### The error occurred while executing a query
### Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
The last packet successfully received from the server was 990 milliseconds ago. The last packet sent successfully to the server was 973 milliseconds ago.
at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:149)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140)
at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:147)
at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:80)
at org.apache.ibatis.binding.MapperProxy$PlainMethodInvoker.invoke(MapperProxy.java:152)
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:85)
at com.sun.proxy.$Proxy18.findAllUser(Unknown Source)
at com.qibu.test.UserDaoTest.testFindAll(UserDaoTest.java:49)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
Caused by: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
The last packet successfully received from the server was 990 milliseconds ago. The last packet sent successfully to the server was 973 milliseconds ago.
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:990)
at com.mysql.jdbc.ExportControlled.transformSocketToSSLSocket(ExportControlled.java:201)
at com.mysql.jdbc.MysqlIO.negotiateSSLConnection(MysqlIO.java:4912)
at com.mysql.jdbc.MysqlIO.proceedHandshakeWithPluggableAuthentication(MysqlIO.java:1663)
at com.mysql.jdbc.MysqlIO.doHandshake(MysqlIO.java:1224)
at com.mysql.jdbc.ConnectionImpl.coreConnect(ConnectionImpl.java:2190)
at com.mysql.jdbc.ConnectionImpl.connectOneTryOnly(ConnectionImpl.java:2221)
at com.mysql.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:2016)
at com.mysql.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:776)
at com.mysql.jdbc.JDBC4Connection.<init>(JDBC4Connection.java:47)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
at com.mysql.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:386)
at com.mysql.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:330)
at java.sql.DriverManager.getConnection(DriverManager.java:664)
at java.sql.DriverManager.getConnection(DriverManager.java:208)
at org.apache.ibatis.datasource.unpooled.UnpooledDataSource.doGetConnection(UnpooledDataSource.java:224)
at org.apache.ibatis.datasource.unpooled.UnpooledDataSource.doGetConnection(UnpooledDataSource.java:219)
at org.apache.ibatis.datasource.unpooled.UnpooledDataSource.getConnection(UnpooledDataSource.java:95)
at org.apache.ibatis.datasource.pooled.PooledDataSource.popConnection(PooledDataSource.java:432)
at org.apache.ibatis.datasource.pooled.PooledDataSource.getConnection(PooledDataSource.java:89)
at org.apache.ibatis.transaction.jdbc.JdbcTransaction.openConnection(JdbcTransaction.java:139)
at org.apache.ibatis.transaction.jdbc.JdbcTransaction.getConnection(JdbcTransaction.java:61)
at org.apache.ibatis.executor.BaseExecutor.getConnection(BaseExecutor.java:337)
at org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.java:86)
at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:62)
at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:325)
at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156)
at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109)
at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:89)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:147)
... 36 more
Caused by: javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Path does not chain with any of the trust anchors
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1949)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:302)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:296)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1509)
at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:216)
at sun.security.ssl.Handshaker.processLoop(Handshaker.java:979)
at sun.security.ssl.Handshaker.process_record(Handshaker.java:914)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1062)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1375)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1403)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1387)
at com.mysql.jdbc.ExportControlled.transformSocketToSSLSocket(ExportControlled.java:186)
... 68 more
Caused by: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Path does not chain with any of the trust anchors
at com.mysql.jdbc.ExportControlled$X509TrustManagerWrapper.checkServerTrusted(ExportControlled.java:302)
at sun.security.ssl.AbstractTrustManagerWrapper.checkServerTrusted(SSLContextImpl.java:922)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1491)
... 76 more
Caused by: java.security.cert.CertPathValidatorException: Path does not chain with any of the trust anchors
at sun.security.provider.certpath.PKIXCertPathValidator.validate(PKIXCertPathValidator.java:153)
at sun.security.provider.certpath.PKIXCertPathValidator.engineValidate(PKIXCertPathValidator.java:79)
at java.security.cert.CertPathValidator.validate(CertPathValidator.java:292)
at com.mysql.jdbc.ExportControlled$X509TrustManagerWrapper.checkServerTrusted(ExportControlled.java:295)
... 78 more
Process finished with exit code -1
最省事的方法是修改数据库连接信息,在链接之后加上“useSSL=false
”,直接禁用SSL连接方式。 jdbc.properties文件
缓存
一级缓存
不需要配置
@Test
public void testfindUserByUId(){
User user = dao.findUserByUId(3);
System.out.println(user);
User user2 = dao.findUserByUId(3);
System.out.println("第二次查询:"+user2);
}
可以看到 只会查一次
二级缓存
要配置,在主配置文件里mybatis-config.xml
<settings>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
</settings>
使用注解配置二级缓存
dao
@CacheNamespace(blocking = true)//mybatis基于注解方式实现配置二级缓存
public interface IUserDao {
实现序列化接口
public class User implements Serializable {
测试
@Test
public void testFindUserByUId2(){
SqlSession sqlSession1 = sessionFactory.openSession();
IUserDao IUserDao1= sqlSession1.getMapper(IUserDao.class);
User user = IUserDao1.findUserByUId(5);
System.out.println("第一次查询"+user);
// 查一遍把一级缓存给它关了
sqlSession1.close();//一级缓存消失
SqlSession sqlSession2 = sessionFactory.openSession();
IUserDao IUserDao2= sqlSession2.getMapper(IUserDao.class);
User user2 = IUserDao2.findUserByUId(5);
System.out.println("第二次查询"+user2);
sqlSession2.close();
// 再查一遍又把一级缓存给它关了
/* 我们来看他会不会发起两次请求。
如果没有发起两次查询,也就意味着二级缓存生效了!!!*/
}
所以注解的方式比xml的方式方便多了,这两种方式都有人用,我们都应该去掌握。
mybatis plus 简单理解就是比mybatis 要稍微方便一点,所谓的方便就是你学完mybatis 之后再去学mybatis plus你会发现很多东西又被简化掉了。自行感兴趣去了解吧