Mybatis
什么是Mybatis
- MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。
- MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
- MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
持久层
数据持久化
- 持久化就是将程序的数据在持久状态和瞬间状态转化的过程
- 持久化就是将我们内存中的数据存在数据库中,
- 数据库(jdbc),IO文件持久化
- 生活:冷藏,罐头
为什么需要持久化
- 内存珍贵
- 有一些对象数据,不能让他丢掉,让他保存起来
持久层
Dao,Service,Controller层…
- 完成持久化工作的代码块
- 层界限十分明显
为什么需要Mybatis
帮助程序员将数据存入到数据库中
方便
传统的jdbc代码太复杂。简化,框架。
帮助程序员将数据存入到数据库中
不用Mybatis也可以,mybatis更容易上手。技术没有高低之分
优点:
最重要得是使用的人很多
第一个Mybatis程序
思路:搭建环境–>导入Mybatis–>编写代码–>测试
搭建环境
-- 创建数据库
create database mybatis
-- 进入数据库
use mybatis
-- 创建表
create table user(
-- not null 不允许为空 PRIMARY KEY唯一主键
id int(20) not null PRIMARY KEY,
-- default null 默认为null
name varchar(30) default null,
pwd varchar(30) default null
-- engine=innodb设置数据库引擎为innodb,
-- default charset=utf8;设置默认字符集为utf-8
)engine=innodb default charset=utf8;
-- 新增数据
insert into user(id,name,pwd)values(1,'星辰','123456');
insert into user(id,name,pwd)values(2,'张三','123456');
insert into user(id,name,pwd)values(3,'李四','123456');
insert into user(id,name,pwd)values(4,'王五','123456');
新建项目
- 新建一个普通的maven项目(不要选择任何模板)
- 清除src目录
- 导入maven依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!-- 夫项目是我们的mybatis,子项目可以引用夫项目的依赖-->
<parent>
<artifactId>mybatis</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mybatis-01</artifactId>
<!-- 这里一定要写,不然会读取不到我们的mapper-->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
</resources>
</build>
</project>
创建配置文件
- 在子模块的src/resources下创建一个mybatis-config.xml,编写mybatis的核心配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--核心配置文件-->
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="0000"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/yunduo/dao/UserDaoMapper.xml"/>
</mappers>
</configuration>
2.编写mybtais工具类
package com.yunduo.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
/**
* Mybatis工具类
* 工厂类
*/
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static{
try{
//使用mybatis第一步,获取SqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}catch (Exception exception){
exception.getMessage();
}
}
//这里的sqlSession对象就相当于jdbc中的Connection对象
//返回sqlSession对象
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
3. 编写代码操作数据库
实体类
package com.yunduo.pojo;
public class User {
private int id;
private String name;
private String pwd;
public User(int id, String name, String pwd) {
this.id = id;
this.name = name;
this.pwd = 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 + '\'' +
'}';
}
}
接口
package com.yunduo.dao;
import com.yunduo.pojo.User;
import java.util.List;
public interface UserDao {
List<User> getUserList();
}
接口对应sql语句
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace命名空间,需要绑定对应的dao接口-->
<mapper namespace="com.yunduo.dao.UserDao">
<!-- 这里ID对应我们接口中的方法 resultType代表返回类型-->
<select id="getUserList" resultType="com.yunduo.pojo.User">
select * from user
</select>
</mapper>
测试
package com.dao;
import com.yunduo.pojo.User;
import com.yunduo.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class UserDao {
@Test
public void test(){
//我们通过工程类创建SqlSession操作数据库的对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
//然后通过sqlsession对象获取我们dao层的接口
//方式一
com.yunduo.dao.UserDao mapper = sqlSession.getMapper(com.yunduo.dao.UserDao.class);
//方式二(不推荐使用)
// List<UserDao> UserDao = sqlSession.selectList("com.yunduo.dao.UserDao.class");
//返回的对象执行自己的接口
List<User> userList = mapper.getUserList();
//如果这里打印出错,那么就是没有配置myabtis-config.xml中mappers中注册到mybatis中,以及要配置maven的pom.xml中build设置过滤
//输出
for (User user : userList) {
System.out.println(user);
}
//关闭数据库对象
sqlSession.close();
}
}
CRUD
- 我们对应的mapper.xml中的namespace一定要和我们对应的接口地址一致,否认绑定不上
- ID:对应的就是我们namespace中的方法名字
- ResyltType:sql语句的返回值
- parameteType:我们sql语句中的参数类型
- 增删改:必须提交事务
Dao
Pojo
package com.yunduo.pojo;
public class User {
private int id;
private String name;
private String pwd;
public User(int id, String name, String pwd) {
this.id = id;
this.name = name;
this.pwd = 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 + '\'' +
'}';
}
}
UserMapper
package com.yunduo.dao;
import com.yunduo.pojo.User;
import java.util.List;
public interface UserDaoMapper {
//查询所有用户
List<User> getUserList();
//根据ID查询用户
User getUserById(User user);
//新增用户
int AddUser(User user);
//根据ID修改用户
int UpdateUserById(User user);
//根据用户ID清除用户
int DeleteUserById(User user);
}
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace命名空间,需要绑定对应的dao接口-->
<mapper namespace="com.yunduo.dao.UserDaoMapper">
<!-- 这里ID对应我们接口中的方法 resultType代表返回类型-->
<select id="getUserList" resultType="com.yunduo.pojo.User" parameterType="com.yunduo.pojo.User">
select * from user
</select>
<select id="getUserById" resultType="com.yunduo.pojo.User" parameterType="com.yunduo.pojo.User">
select * from user where id=#{id}
</select>
<insert id="AddUser" parameterType="com.yunduo.pojo.User">
insert into user(id,name,pwd)values(#{id},#{name},#{pwd})
</insert>
<update id="UpdateUserById" parameterType="com.yunduo.pojo.User">
update user set pwd=#{pwd} where id=#{id}
</update>
<delete id="DeleteUserById" parameterType="com.yunduo.pojo.User">
delete from user where id=#{id}
</delete>
</mapper>
Test
package com.dao;
import com.yunduo.dao.UserDaoMapper;
import com.yunduo.pojo.User;
import com.yunduo.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class UserDao {
//查询所有用户
@Test
public void getUserList() {
//我们通过工程类创建SqlSession操作数据库的对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
//然后通过sqlsession对象获取我们dao层的接口
//方式一
UserDaoMapper mapper = sqlSession.getMapper(UserDaoMapper.class);
//方式二(不推荐使用)
// List<UserDao> UserDao = sqlSession.selectList("com.yunduo.dao.UserDao.class");
//返回的对象执行自己的接口
List<User> userList = mapper.getUserList();
//如果这里打印出错,那么就是没有配置myabtis-config.xml中mappers中注册到mybatis中,以及要配置maven的pom.xml中build设置过滤
//输出
for (User user : userList) {
System.out.println(user);
}
//关闭数据库对象
sqlSession.close();
}
//根据ID查询用户
@Test
public void getUserById() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserDaoMapper user = sqlSession.getMapper(UserDaoMapper.class);
System.out.println(user.getUserById(new User(1,null,null)));
sqlSession.close();
}
//新增用户
@Test
public void AddUser() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserDaoMapper mapper = sqlSession.getMapper(UserDaoMapper.class);
int lao = mapper.AddUser(new User(5, "老六", "1234567"));
//增删改,必须提交事务
sqlSession.commit();
if (lao>0){
System.out.println("新增成功");
}else{
System.out.println("新增失败");
}
}
//根据ID修改用户
@Test
public void UpdateUserById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserDaoMapper mapper = sqlSession.getMapper(UserDaoMapper.class);
int i = mapper.UpdateUserById(new User(5, null, "123456"));
//提交事务
sqlSession.commit();
if (i>0){
System.out.println("修改成功");
}else{
System.out.println("修改失败");
}
}
//根据用户ID清除用户
@Test
public void DeleteUserById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserDaoMapper user = sqlSession.getMapper(UserDaoMapper.class);
int i=0;
try{
i = user.DeleteUserById(new User(5, null, null));
sqlSession.commit();
}catch(Exception e){
e.getMessage();
}finally {
sqlSession.rollback();
}
if (i>0){
System.out.println("清除成功");
}else{
System.out.println("清除失败");
} }
}
利用Map来传递xml中sql参数
Map传递参数,直接在sql中取出来【parameterType=“map”】
对象传递参数,直接在sql中取对象的属性【parameterType=“com.yunduo.pojo.User”】
只有一个基本类型参数的情况下,可以直接在sql中取到【parameterType=“int”】
多个参数用Map或者注解
//根据ID查询用户
User getUserById2(HashMap<String,Object> map);
<!-- 这里如果传递是一个map的话,那么我们传递的参数和key对应即可-->
<select id="getUserById2" resultType="com.yunduo.pojo.User" parameterType="map">
select * from user where id=#{kkk}
</select>
@Test
public void get(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserDaoMapper user = sqlSession.getMapper(UserDaoMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("kkk",1);
User userById2 = user.getUserById2(map);
System.out.println(userById2);
}
模糊查询
我们不能在sql语句中写入%#{value}%
我们需要在我们传递参数的时候给参数加上%参数%即可
方案一
在sql中加通配符进行拼接
<select id="getUserLike" resultType="com.yunduo.pojo.User" parameterType="map">
select * from user where pwd like '%'#{value}'%'
</select>
public void get(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserDaoMapper user = sqlSession.getMapper(UserDaoMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("value","1");
List<User> userLike = user.getUserLike(map);
for (User user1 : userLike) {
System.out.println(user1);
}
}
方案二
在代码中给参数做通配符进行拼接
<select id="getUserLike" resultType="com.yunduo.pojo.User" parameterType="map">
select * from user where pwd like #{value}
</select>
public void get(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserDaoMapper user = sqlSession.getMapper(UserDaoMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("value","%1%");
List<User> userLike = user.getUserLike(map);
for (User user1 : userLike) {
System.out.println(user1);
}
}
配置解析
之前的都只是入门,正真掌握的在下面
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下
configuration(配置) properties(属性) settings(设置) typeAliases(类型别名) typeHandlers(类型处理器) objectFactory(对象工厂) plugins(插件) environments(环境配置) environment(环境变量) transactionManager(事务管理器) dataSource(数据源) databaseIdProvider(数据库厂商标识) mappers(映射器)
环境配置
mybatis可以配置多套数据库
不过要记住,尽管可以配置多个环境,但每个SqlSessionFactory只能选择一套环境
要学会配置多套环境
Mybatis默认的事务管理器是jdbc,默认的链接池是POOLED
属性(properties)
这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。
编写一个外部的properties文件配置我们的数据库链接信息
db.properties
Driver=com.mysql.jdbc.Driver
Url=jdbc:mysql://localhost:3306/mybatis
UserName=root
PassWord=0000
在我们mybatis核心文件mybatis-config.xml中引入我们的db.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 resource="db.properties"/>
<environments default="development">
<environment id="development">
<!-- //事务是采用jdbc-->
<transactionManager type="JDBC"/>
<!-- //数据源用的是POOLED-->
<dataSource type="POOLED">
<!-- 这里的${Driver}引入的是我们外部配置文件的key-->
<property name="driver" value="${Driver}"/>
<property name="url" value="${Url}"/>
<property name="username" value="${UserName}"/>
<property name="password" value="${PassWord}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/yunduo/dao/UserDaoMapper.xml"/>
</mappers>
</configuration>
- 可以直接引入外部文件
- 可以在其中增加一些属性配置
- 如果两个文件有同一个字段,优先使用外部配置文件
类型别名(typeAliases)
类型别名是为java类型对象设置一个短的名字
存在的意义仅在于用来减少类完全限定名的冗余
<typeAliases> <!-- 这里默认别名到一个对象--> <typeAlias alias="user" type="com.yunduo.pojo.User"/> </typeAliases>
也可以指定一个报名,Mybatis会在包名下面搜索需要的Java Bean,比如:
扫码实体类的包,它的默认别名就为了这个类的类名,首字母小写!
<typeAliases> <!-- 这里别名到一个包,包下面的对象别名就是包名--> <package name="com.yunduo.pojo"/> </typeAliases>
怎么选择那种呢
如果实体类少,就使用第一种
如果实体类十分多,建议使用第二种
两个区别就是第一个可以自定义别名,第二个不能自定义别名只能用类的名称当别名
如果你使用的是第二种包扫描,又想自定义别名,那么就可以使用注解来起别名
设置
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为
开启缓存和懒加载
日志
其他配置
插件
- MyBatis Generator Core
- MyBatis Plus
- 通用Mapper
映射器
MapperRegistry:注册绑定我们的Mapper文件
方式一【推荐】
使用绝对定位定位到我们对应的映射器的xml文件
<mappers>
<mapper resource="com/yunduo/dao/UserDaoMapper.xml"/>
</mappers>
方式二
使用class定位我们接口
- 接口和他的mapper配置文件必须同名
- 接口和他的mapper配置文件必须在同一个包下
<mappers>
<mapper class="com.yunduo.dao.UserDaoMapper"/>
</mappers>
方式三
使用包扫描
- 接口和他的mapper配置文件必须同名
- 接口和他的mapper配置文件必须在同一个包下
<mappers>
<package name="com.yunduo.dao"/>
</mappers>
生命周期
生命周期,和作用域,是至关重要的,因为错误的使用会导致非常严重的并发问题
SqlSessionFactoryBuilder
- 一旦创建了 SqlSessionFactory,就不再需要它了
- 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)
SqlSessionFactory
说白了就是数据库链接池
SqlSessionFactory一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例
因此SqlsessionFactory的最佳作用域是应用作用域
最简单的就是使用单例模式或者静态单例模式
SqlSession
链接到线程池的一个请求!
SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
用完之后赶紧关掉
解决属性名和字段名不一致问题
数据库中的字段
新建一个项目,测试实体类字段不一致的情况
这里会发现,我们数据库中的字段和我们实体类里面的字段不同
就会出现问题,查询出来的不一样的字段为null
解决方案
ResultMap
数据库 id name pwd
实体类 id name password
我们将pwd绑定到我们实体类中的password
<!-- 这里的我们使用resultMap getuser是给这个select取名字 -->
<select id="getUserList" resultMap="getuser" parameterType="user">
select * from user
</select>
<!-- 这里使用resultMap标签,id对应select中的resultMap定义的名称,type表示我们需要解决字段不一致的实体类-->
<resultMap id="getuser" type="user">
<!-- 使用result标签,进行映射不一致的属性字段 column对应数据库中的字段,property对应我们修改对应实体类中的属性-->
<result column="pwd" property="password"/>
</resultMap>
- resultmap元素是mybatis中最强大的一个元素
- RestultMao的设计思想是,对应简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语句只需要描述他们的关系就行了
- resultmap最优秀的地方在于,虽然你对他已经相当了解,但是根本就不需要显示地用到他们
日志
日志工厂
如果一个数据库操作,出现了异常,我们需要排错。日志就是最好的助手!
以往我们一般输出东西我们都是System.out.println,Debug
现在:日志工厂来帮我们
- SLF4J
- LOG4J【掌握】
- LOG4J2
- JDK_LOGGING
- COMMONS_LOGGING
- STDOUT_LOGGING【掌握】
- NO_LOGGING
在Mybatis中具体使用哪一个日志实现,在设置中设定!
标准日志
在mybatis核心配置文件中写入
<settings>
<!-- 标准的日志工厂-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
Log4J
什么是log4j
- Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件
- 我们也可以控制每一条日志的输出格式
- 通过定义每一条日志信息的级别
- 可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
使用
导入log4j包
<!-- 注意:1.2.17下载报错,请使用1.2.12 --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.12</version> </dependency>
在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/yunduo.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{yyy-MM-dd}][%c]%m%n #日志输出级别 log4j.logger.org.mybatis=DEBUG log4j.logger.java.sql=DEBUG log4j.logger.java.sql.Statement=DEBUG log4j.logger.java.sql.ResultSet=DEBUG log4j.logger.java.sql.PreparedStatement=DEBUG
log4j默认3种级别
info,debug,error
logger.info("info:启动test1成功");
logger.debug("debug:启动test1成功");
logger.error("error:启动test1成功");
Mybatis怎么使用Log4j打印sql日志?
在mybatis核心配置文件种引入
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
分页
为什么要分页?因为一次性查询很多,会很耗时,就使用分页
方法一:使用limit
方法二:使用代码形式进行分页
//查询所有用户
List<User> getUserList();
<select id="getUserList" resultTypr="user" parameterType="user">
select * from user
</select>
测试
public void query(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
//我们通过mybatis自带的插件RowBounds对象进行分页
RowBounds rowBounds = new RowBounds(0,2);
List<User> user = sqlSession.selectList("com.yunduo.dao.UserDaoMapper.getUserList", null, rowBounds);
for (User user1 : user) {
System.out.println(user1);
}
}
使用注解开发
注解只能完成简单sql语句,复杂的建议使用xml形式
@Select("select * from user")
List<User> queryUserList();
<!-- 映射器注册-->
<mappers>
<mapper class="dao.UserMapper05"/>
</mappers>
public void test1(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper05 mapper = sqlSession.getMapper(UserMapper05.class);
List<User> users = mapper.queryUserList();
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
本质:用的反射机制实现
底层:动态代理。
#号和$符号
#号是预编译是占位符,(prepared statement)编译好SQL语句再取值
$符号是取值后在编译,(createStatement)取值以后再去编译SQL语句
Mybatis执行流程
- Properties加载我们mybatis全局配置文件
- 创建我们SqlSessionFactoryBuilder对象
- SqlSessionFactoryBuilder的builder方法里面会解析mybatis全局配置文件
- 最后得到我们configuration所有的配置信息
- 接着就可以实例化我们的sqlsessionFactory对象
- sqlsessionfactory对象中里面有这个事务管理器,来控制我们sql的事务的
- 接着创建executor执行器,来执行我们crud的操作的,
- 然后检查crud是否执行成功,如果执行失败就回到我们事务管理器回滚,
- 如果执行成功,就提交事务
- 最后关闭
复杂查询
多对一
- 多个学生对应一个老师
- 对于学生这边而言,关联…多个学生,关联一个老师【多对一】
- 对应老师这边而言,集合…一个老师有很多个学生【一对多】
测试环境搭建
- 导入lombok
- 新建实体类Teacher,Student
- 建立对应的Mapper接口
- 建立Mapper.xml文件
- 在核心配置文件中注册我们mapper接口
- 测试查询环境搭建成功
按照查询嵌套处理
按照结果嵌套处理
<resultMap id="getstudent" type="student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!-- 通过查询出来的结果去老师对象中映射-->
<association property="teacher" javaType="teacher">
<!-- 老师对象中的属性 对应 数据库中结果的字段-->
<result property="name" column="teacher"/>
</association>
</resultMap>
<select id="queryStudentList" resultMap="getstudent">
select s.id id,s.name name,t.name teacher from student s,teacher t where s.tid=t.id
</select>
回顾mysql多对一查询方式
- 子查询 (select s.id,s.name,(select t.name from teacher t where s.tid=t.id) teacher from student s)
- 联表查询 (select s.id id,s.name name,t.name teacher from student s,teacher t where s.tid=t.id)
一对多
根据结果嵌套处理
<!--按照结果嵌套查询-->
<resultMap id="queryTeacher" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!-- 复杂属性条件,我们需要单独处理,对象使用association 集合使用collection
javaType="" 指定属性的类型!
集合中的泛型消息,我们使用ofType
-->
<collection property="students" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="stid"/>
</collection>
</resultMap>
<select id="queryTeacherList" resultMap="queryTeacher">
select t.id tid,t.name tname,s.id sid,s.name sname,s.tid stid from teacher t,student s where s.tid=t.id and t.id=#{id}
</select>
Teacher(id=1, name=秦老师, students=[Student(id=1, name=小明, tid=1), Student(id=2, name=小红, tid=1), Student(id=3, name=小张, tid=1), Student(id=4, name=小李, tid=1), Student(id=5, name=小王, tid=1)])
小结
- 关联-association【多对一】
- 集合-collection 【一对多】
- javaType & ofType
- javatype用来指定实体类中属性的类型
- oftypr用来指定映射到List或者集合中的pojo类型,泛型中的约束类型!
注意点:
- 保证SQL的可读性,尽量保证通俗易懂
- 注意一对多和多对一中,属性名和字段的问题!
- 如果问题不好排查错误,可以使用日志,建议使用Log4j
慢SQL 1s 1000s
面试高频
- Mysql引擎
- InnoDB底层原理
- 索引
- 索引优化
动态SQL
什么是动态SQL:动态SQL就是根据不同的条件生成不同的SQL语句
IF
<select id="queryStudentList" resultType="Student" parameterType="map">
select * from student
<where>
<if test="id!=null">
id=#{id}
</if>
<if test="name!=null">
and name=#{name}
</if>
<if test="tid!=null">
and tid=#{tid}
</if>
</where>
</select>
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("id",1);
map.put("name","小明");
map.put("tid",1);
List<Student> students = mapper.queryStudentList(map);
for (Student student : students) {
System.out.println(student);
}
SELECT *
FROM student
WHERE id = 1
AND name = '小明'
AND tid = 1
==>com.yun.dao.StudentMapper.queryStudentList, cost=405ms
Student(id=1, name=小明, tid=1)
choose(when,outerwise)
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
<select id="queryStudentList2" resultType="Student" parameterType="map">
select * from student
<where>
<choose>
<!--这里条件匹配,从上往下,只能匹配一个 -->
<when test="id!=null">
id=#{id}
</when>
<when test="name!=null">
and name=#{name}
</when>
<when test="tid!=null">
and tid=#{tid}
</when>
<!-- 如果上面都不满足,就执行otherwise中的语句 -->
<otherwise>
and id=5
</otherwise>
</choose>
</where>
</select>
public void test2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
// map.put("id",1);
// map.put("name","小明");
List<Student> students = mapper.queryStudentList2(map);
for (Student student : students) {
System.out.println(student);
}
}
load mybatis-log agent success.
==>com.yun.dao.StudentMapper.queryStudentList2
SELECT *
FROM student
WHERE id = 5
==>com.yun.dao.StudentMapper.queryStudentList2, cost=294ms
Student(id=5, name=小王, tid=1)
Trim(where set)
前面几个例子已经方便地解决了一个臭名昭著的动态 SQL 问题。现在回到之前的 “if” 示例,这次我们将 “state = ‘ACTIVE’” 设置成动态条件,看看会发生什么。
select * from student
<where>
<if test="id!=null">
id=#{id}
</if>
<if test="name!=null">
and name=#{name}
</if>
<if test="tid!=null">
and tid=#{tid}
</if>
</where>
@Test
public void test1(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("id",1);
map.put("name","小明");
map.put("tid",1);
//这里我们就算什么都不传递,它会自动去掉where
List<Student> students = mapper.queryStudentList(map);
for (Student student : students) {
System.out.println(student);
}
}
load mybatis-log agent success.
==>com.yun.dao.StudentMapper.queryStudentList
SELECT *
FROM student
WHERE id = 1
AND name = '小明'
AND tid = 1
==>com.yun.dao.StudentMapper.queryStudentList, cost=310ms
Student(id=1, name=小明, tid=1)
Set
通常用于update语句
<update id="updateStudentByid" parameterType="map">
update student
<set>
<if test="name!=null">
name=#{name},
</if>
<if test="tid!=null">
tid=#{tid},
</if>
</set>
where id=#{id}
</update>
public void test3(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("id",1);
map.put("name","星辰");
mapper.updateStudentByid(map);
//提交事务
sqlSession.commit();
sqlSession.close();
}
UPDATE student
SET name = '星辰'
WHERE id = 1
SQL片段
有的时候,我们可能会将一些功能的部分抽取出来,方便复用【工具类】
- 使用sql标签抽取出公共部分
- 在需要使用的地方,使用include标签引用即可
<!-- sql工具类-->
<sql id="if-sql">
<if test="id!=null">
id=#{id}
</if>
<if test="name!=null">
and name=#{name}
</if>
<if test="tid!=null">
and tid=#{tid}
</if>
</sql>
<!-- 使用include标签引用-->
<select id="queryStudentList" resultType="Student" parameterType="map">
select * from student
<where>
<include refid="if-sql"></include>
</where>
</select>
public void test1(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("id",1);
//这里我们就算什么都不传递,它会自动去掉where
List<Student> students = mapper.queryStudentList(map);
for (Student student : students) {
System.out.println(student);
}
}
SELECT *
FROM student
WHERE id = 1
==>com.yun.dao.StudentMapper.queryStudentList, cost=364ms
Student(id=1, name=星辰, tid=1)
注意:
- 最好基于单张表查询
- 不要存在where标签
Foreach
需求,我想查出id为1,5,9,11的用户信息
select * from student where id in(1,5,9,11)
但是这样参数是固定的,我们需要传递一个可变长的list参数。
<!-- select * from student where id in(1,2,5)-->
<select id="querystudent" parameterType="list" resultType="student">
select * from student
<where>
<foreach collection="list" item="id"
open="id in(" separator="," close=")">
#{id}
</foreach>
</where>
</select>
public void test4(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
ArrayList<Integer> arrayList = new ArrayList<Integer>();
arrayList.add(1);
arrayList.add(5);
List<Student> querystudent = mapper.querystudent(arrayList);
for (Student student : querystudent) {
System.out.println(student);
}
}
SELECT *
FROM student
WHERE id IN (1, 5)
==>com.yun.dao.StudentMapper.querystudent, cost=255ms
Student(id=1, name=星辰, tid=1)
Student(id=5, name=小王, tid=1)
缓存
查询:链接数据库,耗资源
一次查询的结果,给他暂存在一个可以直接取到的地方!---->内存:缓存
我们再次查询相同数据的时候,直接走缓存,就不用走数据库了
什么是缓存?
- 是存放在内存中的临时数据
- 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(数据库)查询,而是直接从缓存中查询,从而提高查询效率,解决了高并发系统问题。
为什么使用缓存?
- 减少和数据库交互的次数,减少系统的开箱,提高系统效率
什么样的数据能使用缓存
- 经常查询并且不改变的数据【可以使用缓存】
- 增删改是不能用缓存的【不难使用缓存】
怎么使用缓存
- Mybatis包含了一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
- Mybatis系统中默认定义了两级缓存:一级缓存,二级缓存
- 默认情况下,只有一级缓存开启,(Sqlsession级别的缓存,也成为本地缓存)
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存
- 为了提高高扩展性,Mybatis定义了缓存接口cache,我们可以通过实现cache接口来自定义二级缓存
一级缓存
仅在一个sqlsession中查询相同的才有效
- 开启日志
- 测试链接一个Sqlsession查询两次相同记录
- 查看日志输出
可以发现,myabtis中只执行了一次sql,那么第二次就是从缓存中拿取的
SQL
当我们查询不一样的数据的时候,会查询出两条sql
缓存失效的情况
如果当前sqlsession中有查询又有增删改,那么就会出现缓存失效情况
查询出不同的东西
增删改可能会对我们查询的数据做出更改,所以会清除当前缓存
查询不同Mapper
手动清除缓存
sqlSession.clearCache();
小结
Mybatis一级缓存是默认开启的,只在一次sqlsession中开启和关闭之间是有效的。
一级缓存就是一个map,第一次获取时候put进去,后续在取得时候直接get
二级缓存
生效条件,当前sqlsession死亡后,才会将当前得数据保存进二级缓存中。
- 二级缓存也叫全局缓存,一级缓存作用域太低,所以诞生了二级缓存
- 基于namespace级别得缓存,一个名称空间,对应一个二级缓存;
- 工作机制
- 一个会话查询一条语句,这个数据就会被放在当前会话得一级缓存中;
- 如果当前会话关闭了,这个会话对应得一级缓存就没了;但是我们想要得是,会话关闭了,一级缓存中得数据被保存到二级缓存中;
- 新的会话查询信息,就可以从二级缓存中获取内容;
- 不同得mapper查出得数据会被放在对应得缓存(map)中
使用
在mybatis配置中心开启
<!-- 开启缓存--> <setting name="cacheEnabled" value="true"/>
在需要使用二级缓存得Mapper中添加开启
<!--方法一:--> <cache/> <!--方法二,自定义参数--> <!-- 开启缓存 eviction清除策略,flushInterval刷新时间,size缓存大小,readOnly是否只读--> <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/> />
问题:
使用缓存,一定要得我们pojo进行序列化,实现Serializable接口
测试
mapper
<!-- 开启缓存 eviction清除策略,flushInterval刷新时间,size缓存大小,readOnly是否只读-->
<cache/>
<resultMap id="queryUserListmap" type="com.yunduo.pojo.User">
<result property="password" column="pwd"/>
</resultMap>
<select id="queryUserList" resultMap="queryUserListmap" parameterType="com.yunduo.pojo.User">
select * from user where id=#{id}
</select>
pojo
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private int id;
private String name;
private String password;
}
test
@Test
public void test1(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserList(1);
System.out.println(user);
sqlSession.close();
SqlSession sqlSession1 = MybatisUtils.getSqlSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = mapper1.queryUserList(1);
System.out.println(user1);
sqlSession.close();
System.out.println(user==user1);
}
结果
Cache Hit Ratio [com.yunduo.dao.UserMapper]: 0.0
Opening JDBC Connection
Created connection 127702987.
==> Preparing: select * from user where id=?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, 星辰, 123456
<== Total: 1
==>com.yunduo.dao.UserMapper.queryUserList
SELECT *
FROM user
WHERE id = 1
==>com.yunduo.dao.UserMapper.queryUserList, cost=316ms
User(id=1, name=星辰, password=123456)
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@79c97cb]
Returned connection 127702987 to pool.
Cache Hit Ratio [com.yunduo.dao.UserMapper]: 0.5
User(id=1, name=星辰, password=123456)
false
小结
- 只要开启了二级缓存,在同一个Mapper下才有效
- 所有得数据都会先放在一级缓存中;
- 只有当会话提交,或者关闭得时候,才会提交到二级缓存中!
缓存原理
- 先查询二级缓存有没有
- 在查询一级缓存有没有
- 最后查询数据库
自定义缓存
Ehcache是一种广泛用于Java的分布式缓存,主要面向通用缓存
使用
导包
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache --> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.1.0</version> </dependency>
修改mapper中cache中的type
<cache type="org.mybatis.caches.ehcache"/>
- 添加配置文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<!--
diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
user.home – 用户主目录
user.dir – 用户当前工作目录
java.io.tmpdir – 默认临时文件路径
-->
<diskStore path="java.io.tmpdir/Tmp_EhCache"/>
<!--
defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
-->
<!--
name:缓存名称。
maxElementsInMemory:缓存最大数目
maxElementsOnDisk:硬盘最大缓存个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
overflowToDisk:是否保存到磁盘,当系统当机时
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
FIFO,first in first out,这个是大家最熟的,先进先出。
LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
-->
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>
<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>