MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。
IBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层(Dao 数据访问层)框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAO)
下载地址: MyBatis下载地址:https://github.com/mybatis/mybatis-3/releases
使用版本:3.4.5
Hibernate 冬眠 全自动框架 SQL语句可以自动生成,不用人工书写SQL! 灵活度,性能差,笨重。SQL定制麻烦!
自动挡汽车汽车!
-----------------------------------------------------------------------------------------------------
MyBatis 半自动 SQL语句还是需要自己书写,后期有一些插件可以自动生成SQL! 灵活 定制SQL!
手自一体汽车!
MyBatis Plus 插件!
ORM概念: Object Ralation Mapping 对象关系映射 框架
数据库表 和 程序中的实体类 有对应关系(映射关系)的框架,叫做ORM框架(对象关系映射)
数据库表 tb_user ------>实体类 User
数据库表中字段 username----->实体类的属性 username
数据库表中字段的类型 varchar(20) ----->实体类中属性的类型 String
数据库表 和 程序实体类 有对应关系的持久层框架 就叫ORM框架!
常见的ORM框架哪些:
MyBatis 典型的ORM框架,数据库表和程序实体类存在映射关系的框架! SSM SpringMvc+Spring+MyBatis
Hibernate SSH Struts2+Spring+Hibernate
Spring Data JPA
......
简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql基本上可以实现我们不使用数据访问框架可以实现的所有功能,或许更多。
插件丰富,开源框架,有很多第三方插件,辅助开发,甚至MyBatis自定义插件!
MyBatis Plus插件!
JDK环境:jdk1.8
IDE环境:IDEA
数据库环境:MySQL 5.5.X
MyBatis:3.4.5
maven:3.6.X
新建数据库:mybatis
字符集选择:utf8 – UTF-8 Unicode
排序规则选择:utf8_general_ci
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(32) NOT NULL COMMENT '用户名称',
`birthday` date DEFAULT NULL COMMENT '生日',
`sex` char(1) DEFAULT NULL COMMENT '性别',
`address` varchar(256) DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `tb_user` VALUES ('1', '王五', '2018-09-06', '1', '四川成都');
INSERT INTO `tb_user` VALUES ('10', '张三', '2014-07-10', '1', '北京市');
INSERT INTO `tb_user` VALUES ('16', '张小明', '2018-09-06', '1', '河南郑州');
INSERT INTO `tb_user` VALUES ('22', '陈小明', '2018-09-05', '1', '河南郑州');
INSERT INTO `tb_user` VALUES ('24', '张三丰', '2018-09-13', '1', '河南郑州');
INSERT INTO `tb_user` VALUES ('25', '陈小明', '2018-09-12', '1', '河南郑州');
INSERT INTO `tb_user` VALUES ('26', '王五', '2018-09-05', '0', '河南郑州');
先创建一个普通的maven工程,把这个工程作为父工程
在父工程上点击右键选择新建—>module 创建子工程
父工程中的src没有卵用,直接删除!代码写在子工程中
注意:公司里边一般都是聚合项目
在父工程的pom.xml中导入依赖
<dependencies>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.4.5version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.15version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.16.18version>
<scope>providedscope>
dependency>
dependencies>
src/main/java ->com.dream.pojo
用lombok的注解
package com.dream.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String username;
private String birthday;//Mysql中Date程序中可以String表示,好处就是不用类型转换
private String sex;
private String address;
}
src/main/resources -> mybatis-config.xml
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="java2105">
<environment id="java2105">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
mappers>
configuration>
Mapper层相当于MVC模式下的Dao层
src/main/java -> com.dream.mapper -> UserMapper
package com.dream.mapper;
import com.dream.pojo.User;
import java.util.List;
public interface UserMapper {
List<User> findUsers();
}
src/main/resources -> com.dream/mappers -> mapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dream.mapper.UserMapper">
<select id="findUsers" resultType="com.dream.pojo.User">
select * from tb_user
select>
mapper>
src/main/java -> com.dream.mapper -> UserMapperImpl
package com.dream.mapper;
import com.dream.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class UserMapperImpl implements UserMapper {
public List<User> findUsers() {
SqlSession sqlSession = null;
try {
//加载并读取配置文件
String resources = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resources);
//每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过sqlSessionFactory工厂获取SqlSession对象!
//SqlSession对象不是以前HttpSession,SqlSession类似于数据库JDBC里面的Connection
sqlSession = sqlSessionFactory.openSession();
//执行查询集合方法: namespace+id
List<User> users = sqlSession.selectList("com.dream.mapper.UserMapper.findUsers");
return users;
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭资源
sqlSession.close();
}
return null;
}
}
src/test/java -> com.dream.test -> TestMyBatis
package com.dream.test;
import com.dream.pojo.User;
import com.dream.mapper.UserMapper;
import com.dream.mapper.UserMapperImpl;
import org.junit.Test;
import java.util.List;
public class TestMybatis {
@Test
public void testFindusers(){
UserMapper um=new UserMapperImpl();
List<User> users = um.findUsers();
for (User user : users) {
System.out.println(user);
}
}
}
src/main/resources -> jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true
jdbc.username=root
jdbc.password=root
src/main/resources -> mybatis-config.xml
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="jdbc.properties"/>
<environments default="java2105">
<environment id="java2105">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
mappers>
configuration>
Mapper.xml文件中实体类名字默认必须使用全名称,类似com.dream.pojo.User,为了书写简洁一些,可以给实体类取别名
方式1:给每个实体类单独取别名
不建议使用,实体类多了代码就要写很多
src/main/resources -> mybatis-config.xml
<typeAliases>
<typeAlias type="com.dream.pojo.User" alias="User"/>
typeAliases>
方式2:给所有实体类包下所有实体类统一的取别名
建议使用
src/main/resources -> mybatis-config.xml
<typeAliases>
<package name="com.dream.pojo"/>
typeAliases>
src/main/resources -> com.dream/mappers -> mapper.xml
<select id="findUsers" resultType="User">
select * from tb_user
select>
src/main/resources -> mybatis-config.xml
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
mappers>
src/main/resources -> mybatis-config.xml
前提条件:1.接口的名字必须和mapper.xml文件名字相同
2.接口的文件和mapper.xml必须在同一个路径下
文件满足以上两个后,仍然无法配置成功。原因是:
如果xml文件在src下,但是不在resources下面默认是不会编译的
所以解决方案有如下两个:
方式1:每个mapper.xml单独配置
不建议使用
src/main/resources -> mybatis-config.xml
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
mappers>
在maven项目中配置xml规则
建议使用
MyBatis -> pom.xml
<build>
<resources>
<resource>
<directory>src/main/javadirectory>
<includes>
<include>**/*.xmlinclude>
<include>**/*.propertiesinclude>
includes>
resource>
<resource>
<directory>src/main/resourcesdirectory>
<includes>
<include>**/*.xmlinclude>
<include>**/*.propertiesinclude>
includes>
resource>
resources>
build>
1.Mapper的namespace必须和mapper接口的全路径一致
2.Mapper接口的方法名必须和sql定义的id一致
3.Mapper接口中方法的输出参数类型必须和sql定义的resultType一致
User findById(Integer id);
在MyBatis中,占位符:#{}、${},占位符在JDBC底层最终还是翻译为?
<select id="findById" resultType="com.dream.pojo.User" parameterType="integer">
select * from tb_user where id=#{id}
select>
public User findById(Integer id) {
SqlSession sqlSession=null;
try {
String resources="mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resources);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSession = sqlSessionFactory.openSession();
User u= sqlSession.selectOne("com.dream.mapper.UserMapper.findById",id);
return u;
} catch (IOException e) {
e.printStackTrace();
} finally {
sqlSession.close();
}
return null;
}
@Test
public void testFindById(){
User user = um.findById(10);
System.out.println(user);
}
src/main/java -> com.dream.utils ->MyBatisUtils
package com.dream.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class MyBatisUtils {
/**
* 01-获取SqlSession
* @return
*/
public static SqlSession getSqlSession(){
SqlSession sqlSession = null;
try {
//加载并读取配置文件
String resources = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resources);
//每个基于MyBatis的应用都是以一个SqlSessionFactory的实例为核心的
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过sqlSessionFactory工厂获取SqlSession对象
//SqlSession对象不是以前HttpSession,SqlSession类似于数据库JDBC里面的Connection
//true:自动提交事务
sqlSession = sqlSessionFactory.openSession(true);
} catch (IOException e) {
e.printStackTrace();
}
return sqlSession;
}
/**
* 02-关闭SqlSession
* @param sqlSession
*/
public static void closeSqlSession(SqlSession sqlSession){
if (sqlSession != null){
sqlSession.close();
}
}
}
int addUser(User u);
insert 新增标签,MyBatis新增、删除、更新默认都返回int,也就是数据库受影响行数,不用写返回值!
MyBatis参数类型可以省略! parameterType=“com.bruce.bean.User”
#{username}:中括号中的属性要与实体类的属性值相同
<insert id="addUser">
insert into tb_user values (null,#{username},#{birthday},#{sex},#{address})
insert>
MyBatis需要提交事务 的操作:增加、删除、更新
手动提交事务:sqlSession.commit();
手动回滚事务:sqlSession.rollback();
/**
* 增加、删除、更新:MyBatis需要提交事务
* @param u
* @return
*/
public int addUser(User u) {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.getSqlSession();
//执行新增
int insert = sqlSession.insert("com.dream.mapper.UserMapper.addUser", u);
//手动提交事务
sqlSession.commit();
return insert;
} catch (Exception e){
//手动回滚事务
sqlSession.rollback();
e.printStackTrace();
} finally {
MyBatisUtils.closeSqlSession(sqlSession);
}
return 0;
}
@Test
public void testInsert() {
User u = new User(null, "隔壁老王", "1990-10-10", "男", "隔壁的");
int count = um.addUser(u);
System.out.println(count>0?"新增成功":"新增失败");
}
方便开发人员调试程序,理解程序。
<settings>
<setting name="logImpl" value="STDOUT_LOGGING" />
settings>
src/main/resources -> mybatis-config.xml
注意:插入必须在之后添加,必须在之后添加。
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="jdbc.properties"/>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING" />
settings>
<typeAliases>
<package name="com.dream.pojo"/>
typeAliases>
<environments default="java2105">
<environment id="java2105">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
dataSource>
environment>
environments>
<mappers>
<package name="com.dream.mapper"/>
mappers>
configuration>
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Opening JDBC Connection
Created connection 1373810119.
==> Preparing: insert into tb_user values (null,?,?,?,?)
==> Parameters: 隔壁老王(String), 1990-10-10(String), 男(String), 隔壁的(String)
<== Updates: 1
新增成功
1.实现类的代码大部分都是相似的冗余代码;
2.每次在接口中填加一个接口,实现必须重写,太鸡肋!
3.实现类会增加代码量,sql语句地址写在方法中是一串字符串,容易写错!
1.Mapper接口不能直接使用,因为接口不能创建对象!
2.UserMapper um = new UserMapperImpl();
底层使用JDK动态代理技术给Mapper接口生成了实现类对象
SqlSession sqlSession = MyBatisUtils.getSqlSession();
//sqlSession.getMapper(UserMapper.class):在框架底层,MyBatis使用JDK动态代理设置模式,给UserMapper接口生成了一个实现类!
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
src/test/java -> com.dream.test -> TestMyBatis
@Test
public void testInsert() {
User u = new User(null, "老吴", "1990-10-10", "男", "隔壁的");
int count = um.addUser(u);
// 手动提交事务
sqlSession.commit();
System.out.println(count > 0 ? "新增成功" : "新增失败");
}
src/main/java -> com.dream.utils -> MyBatisUtils
//默认是false,这里加个true,true:自动提交事务
sqlSession = sqlSessionFactory.openSession(true);
package com.dream.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class MyBatisUtils {
/**
* 01-获取SqlSession
* @return
*/
public static SqlSession getSqlSession(){
SqlSession sqlSession = null;
try {
//加载并读取配置文件
String resources = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resources);
//每个基于MyBatis的应用都是以一个SqlSessionFactory的实例为核心的
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过sqlSessionFactory工厂获取SqlSession对象
//SqlSession对象不是以前HttpSession,SqlSession类似于数据库JDBC里面的Connection
//true:自动提交事务
sqlSession = sqlSessionFactory.openSession(true);
} catch (IOException e) {
e.printStackTrace();
}
return sqlSession;
}
/**
* 02-关闭SqlSession
* @param sqlSession
*/
public static void closeSqlSession(SqlSession sqlSession){
if (sqlSession != null){
sqlSession.close();
}
}
}
int deleteUser(Integer id);
<delete id="deleteUser">
delete from tb_user where id=#{id}
delete>
@Test
public void testDelete(){
int count = um.deleteUser(10);
System.out.println(count > 0 ? "删除成功" : "删除失败");
}
int updateById(User u);
<!--
update 更新标签,MyBatis新增、删除、更新默认都返回int,也就是数据库受影响行数,不用写返回值!
-->
<update id="updateById">
update tb_user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}
</update>
@Test
public void testUpdateById(){
//先把这个对象查询出来
User u = userMapper.findById(16);
u.setUsername("小舞");
u.setSex("女");
u.setAddress("斗罗大陆");
//更新
int count = userMapper.updateById(u);
System.out.println(count>0?"更新成功":"更新失败");
}
//根据姓名和性别查询
List<User> findUserByUserNameAndSex(User u);
<select id="findUserByUserNameAndSex" resultType="User">
select * from tb_user where username=#{username} and sex=#{sex}
select>
@Test
public void testFindUserByUserNameAndSex(){
User u=new User();
u.setUsername("小舞");
u.setSex("女");
List<User> users = userMapper.findUserByUserNameAndSex(u);
for(User user:users){
System.out.println(user);
}
}
//根据出生日期区间查询 1991-01-01 ----- 1993-12-12
List<User> findUsersByBirthday(String startBirthday,String endBirthday);
这里用param1和param2
@Test
public void testFindUsersByBirthday(){
List<User> users = userMapper.findUsersByBirthday("2018-09-05", "2018-09-12");
for (User user:users) {
System.out.println(user);
}
}
这里用**@Param(“a”)和@Param(“b”)**
这样就不用在意顺序
//根据出生日期区间查询 1991-01-01 ----- 1993-12-12
List<User> findUsersByBirthdays(
@Param("a") String startBirthday,
@Param("b") String endBirthday);
<select id="findUsersByBirthday" resultType="User">
select * from tb_user where birthday between #{a} and #{b}
select>
@Test
public void testFindUsersByBirthdays(){
List<User> users = userMapper.findUsersByBirthdays("2018-09-05", "2018-09-12");
for (User user:users) {
System.out.println(user);
}
}
使用场景:如果查询的参数多,那么建议使用Map带值
//参数多的情况下
List<User> findUserByBirthdayAndSex(Map<String,Object> map);
<select id="findUserByBirthdayAndSex" resultType="User">
select * from tb_user where birthday between #{a} and #{b} and sex=#{sex}
select>
@Test
public void testFindUserByBirthdayAndSex(){
Map<String,Object> map = new HashMap<String,Object>();
map.put("a","1991-01-01");
map.put("b","1995-01-01");
map.put("sex","女");
List<User> users = userMapper.findUserByBirthdayAndSex(map);
for(User user:users){
System.out.println(user);
}
}
//查询男生数量
int getCountBySex(String sex);
<select id="getCountBySex" resultType="int">
select count(id) from tb_user where sex=#{sex}
select>
@Test
public void testGetCountBySex(){
int count = userMapper.getCountBySex("男");
System.out.println("男生的数量是:" + count);
}
//查询男生的 最大生日 和 最小的生日
Map<String,Object> getMaxAndMinBirthday(String sex);
<select id="getMaxAndMinBirthday" resultType="map">
select max(birthday) 'max',min(birthday) 'min' from tb_user where sex=#{sex}
select>
@Test
public void testGetMaxAndMinBirthday(){
Map<String,Object> map = userMapper.getMaxAndMinBirthday("男");
System.out.println("最大的生日是:"+map.get("max"));
System.out.println("最小的生日是:"+map.get("min"));
}
//查询男生 和 女生 的 最大生日 和 最小的生日
//select max(birthday),min(birthday),sex from tb_user GROUP BY sex;
List<Map<String,Object>> getInformation();
<select id="getInformation" resultType="map">
select max(birthday) 'max',min(birthday) 'min',sex from tb_user group by sex
select>
@Test
public void testGetInformation(){
List<Map<String,Object>> mapList = userMapper.getInformation();
for(Map<String,Object> map:mapList){
System.out.println("最大生日:"+map.get("max"));
System.out.println("最小生日:"+map.get("min"));
System.out.println("性别:"+map.get("sex"));
System.out.println("-------------------------");
}
}
//分页查询
int getCount(Map<String,Object> map);
List<User> findPage(Map<String,Object> map);
<select id="getCount" resultType="int">
select count(id) from tb_user where sex=#{sex}
select>
<select id="findPage" resultType="User">
select * from tb_user where sex=#{sex} limit #{startIndex},#{pageIndex}
select>
/**
* 测试分页查询
*/
@Test
public void testPage(){
int pageIndex=2; //当前页码
int pageSize=5; //页面大小
String sex="男"; //查询条件
Map<String,Object> map=new HashMap<String, Object>();
map.put("startIndex",(pageIndex-1)*pageSize);
map.put("pageSize",pageSize);
map.put("sex",sex);
int count=um.getCount(map);
int pageCount=count%pageSize==0?count/pageSize:count/pageSize+1;
List<User> users = um.findPage(map);
System.out.println("当前页码:"+pageIndex);
System.out.println("页面大小:"+pageSize);
System.out.println("总页数:"+pageCount);
System.out.println("总条数:"+count);
System.out.println("-------------------------------------------");
for (User user : users) {
System.out.println(user);
}
}
方式1:
//查询名字中包含"小"的学生
List<User> findUsersByName(String username);
方式2:
@Param(“a”)
//查询名字中包含"小"的学生
List<User> findUsersByNames(@Param("a") String username);
方式1:
<select id="findUsersByName" resultType="User">
select * from tb_user where username like #{username}
select>
方式2:
<select id="findUsersByNames" resultType="User">
select * from tb_user where username like '%${a}%'
select>
方式1:
传参时加入%%
@Test
public void testLike1(){
List<User> users = um.findUsersByName("%小%");
for (User user : users) {
System.out.println(user);
}
}
方式2:
@Test
public void testLike2(){
List<User> users = um.findUsersByName1("小");
for (User user : users) {
System.out.println(user);
}
}
方式1:性能高(因为只执行了1条SQL语句),只适用于数据库ID自增数据库,一般用于MySQL数据库。
insert 新增标签,MyBatis新增、删除、更新默认都返回int,也就是数据库受影响行数,不用写返回值!
parameterType=“com.bruce.bean.User”:MyBatis参数类型可以省略
useGeneratedKeys=“true”:开启自增长映射
keyProperty=“id” :指定id所对应对象中的属性名
<insert id="addUser" parameterType="User" keyProperty="id" useGeneratedKeys="true">
INSERT INTO tb_user VALUES (null,#{username},#{birthday},#{sex},#{address})
insert>
方式2:性能差(因为执行了2条SQL语句,先执行新增,再执行查询),适用于广泛数据库,一般用于Orange数据库。
keyProperty=“id” :指定id所对应对象中的属性名
resultType=“int”:返回值类型
order=“AFTER”:在新增之后执行查询id语句,如果写BEFORE,则查询结果就是0
**SELECT LAST_INSERT_ID()**
<insert id="addUser" parameteType="User">
<selectKey keyProperty="id" resultType="int" order="AFTER">
SELECT LAST_INSERT_ID()
selectKey>
INSERT INTO tb_user VALUES (null,#{username},#{birthday},#{sex},#{address})
insert>
#{ }:实现的是sql语句的预处理参数,之后执行sql中用**?号代替,使用时不需用关注数据类型**,MyBatis自动实现数据类型的转换。更加安全,可以防止SQL注入。
${ }:实现sql语句的直接拼接,不做数据类型转换,需要自行判断数据类型,不能防止SQL注入。
补充:1.Statement和PrepareStatement 的区别:
答:
2.什么是SQL注入,如何防止SQL注入?
答:
应用场景:根据用户的查询的条件,SQL会动态的变化,也就是说SQL语句不是固定,灵活度更高,运行效率高!
准备测试的数据库tb_employee
DROP TABLE IF EXISTS `tb_employee`;
CREATE TABLE `tb_employee` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
`email` varchar(50) COLLATE utf8_bin DEFAULT NULL,
`gender` char(1) COLLATE utf8_bin DEFAULT NULL,
`age` int DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin;
-- ----------------------------
-- Records of tb_employee
-- ----------------------------
INSERT INTO `tb_employee` VALUES ('7', 'Jerry', '[email protected]', '1', '33');
INSERT INTO `tb_employee` VALUES ('8', '刘备', '[email protected]', '1', '18');
INSERT INTO `tb_employee` VALUES ('9', '诸葛亮', '[email protected]', '1', '18');
INSERT INTO `tb_employee` VALUES ('10', '曹操', '[email protected]', '1', '18');
INSERT INTO `tb_employee` VALUES ('11', '小琼1', '[email protected]', '0', '18');
INSERT INTO `tb_employee` VALUES ('12', '小琼2', '[email protected]', '0', '18');
INSERT INTO `tb_employee` VALUES ('13', '小琼3', '[email protected]', '0', '18');
INSERT INTO `tb_employee` VALUES ('14', '小琼4', '[email protected]', '0', '18');
INSERT INTO `tb_employee` VALUES ('15', '小琼5', '[email protected]', '0', '18');
重点掌握
EmployeeMapper.java接口
List<Employee> findEmpByNameAndGender(Employee employee);
EmployeeMapper.xml定义SQL语句
这里的1=1也可以写成2=2,目的是让代码不报错
<select id="findEmpByNameAndGender" resultType="Employee">
select * from tb_employee where 1=1
<if test="name!=null and gender!=''">
and name=#{name}
if>
<if test="name!=null and gender!=''">
and gender=#{gender}
if>
select>
TestMyBatis.java测试类
/**
* select * from tb_employee where 1=1
* select * from tb_employee where 1=1 and name=?
* select * from tb_employee where 1=1 and gender=?
* select * from tb_employee where 1=1 and name=? and gender=?
*/
@Test
public void testFindEmpByNameAndGender(){
Employee employee = new Employee();
employee.setName("Jerry");
employee.setGender("1");
List<Employee> employees = em.findEmpByNameAndGender(employee);
for (Employee e:employees) {
System.out.println(e);
}
}
重点掌握
上面的1=1可读性不高,因此用IF+WHERE标签配合使用,可读性更高。
EmployeeMapper.java接口
List<Employee> findEmpByNameAndGender(Employee employee);
EmployeeMapper.xml定义SQL语句
注:在if中会默认删除第一个and
<select id="findEmpByNameAndGender" resultType="Employee">
select * from tb_employee
<where>
<if test="name!=null and name!=''">
name=#{name}
if>
<if test="gender!=null and gender!=''">
and gender=#{gender}
if>
where>
select>
TestMyBatis.java测试类
/**
* select * from tb_employee where 1=1
* select * from tb_employee where 1=1 and name=?
* select * from tb_employee where 1=1 and gender=?
* select * from tb_employee where 1=1 and name=? and gender=?
*/
@Test
public void testFindEmpByNameAndGender(){
Employee employee = new Employee();
employee.setName("Jerry");
employee.setGender("1");
List<Employee> employees = em.findEmpByNameAndGender(employee);
for (Employee e:employees) {
System.out.println(e);
}
}
重点掌握
应用场景:根据用户输入条件动态的改变SQL更新的字段
EmployeeMapper.java接口
int updateEmp(Employee employee);
EmployeeMapper.xml定义SQL语句
注:如果有多余的逗号(,)会自动剔除,建议都加上逗号,不加逗号会报错
<update id="updateEmp">
update tb_employee
<set>
<if test="name!=null and name!=''">
name=#{name},
if>
<if test="email!=null and email!=''">
email=#{email},
if>
<if test="gender!=null and gender!=''">
gender=#{gender},
if>
<if test="age!=null">
age=#{age},
if>
set>
update>
TestMyBatis.java测试类
@Test
public void testUpdate(){
Employee employee=new Employee();
employee.setId(5);
employee.setName("赵四");
//employee.setGender("1");
int count = em.updateEmp(employee);
System.out.println(count>0?"更新成功":"更新失败");
}
EmployeeMapper.java接口
List<Employee> selectUserByChoose(Employee employee);
EmployeeMapper.xml定义SQL语句
when相当于case,otherwise相当于条件
注:相当于Java中的switch语法,条件只能满足一个
<select id="selectUserByChoose" resultType="Employee">
select * from tb_employee
<where>
<choose>
<when test="name!=null and name!=''">
and name=#{name}
when>
<when test="gender!=null and gender!=''">
and gender=#{gender}
when>
<otherwise>
and age>18
otherwise>
choose>
where>
select>
TestMyBatis.java测试类
@Test
public void testselectUserByChoose(){
Employee employee=new Employee();
employee.setName("小A");
//employee.setGender("1");
List<Employee> employees =em.selectUserByChoose(employee);
for (Employee emp : employees) {
System.out.println(emp);
}
}
用法1:
EmployeeMapper.java接口
List<Employee> selectUserByTrim(Employee employee);
EmployeeMapper.xml定义SQL语句
prefix:前缀
prefixoverride:去掉第一个and或者是or
<select id="selectUserByTrim" resultType="Employee">
select * from tb_employee
<trim prefix="WHERE" prefixOverrides="AND|OR">
<if test="name!=null and name!=''">
and name=#{name}
if>
<if test="gender!=null and gender!=''">
and gender=#{gender}
if>
trim>
select>
TestMyBatis.java测试类
@Test
public void selectUserByTrim(){
Employee employee=new Employee();
employee.setName("小A");
//employee.setGender("1");
List<Employee> employees = em.selectUserByTrim(employee);
for (Employee emp : employees) {
System.out.println(emp);
}
}
用法2:
EmployeeMapper.java接口
int updateEmp1(Employee employee);
EmployeeMapper.xml定义SQL语句
prefix:前缀
suffixOverrides(","):去掉最后一个逗号(也可以是其他的标记,就像是上面前缀中的and一样)
<update id="updateEmp1">
update tb_employee
<trim prefix="SET" suffixOverrides=",">
<if test="name!=null and name!=''">
name=#{name},
if>
<if test="email!=null and email!=''">
email=#{email},
if>
<if test="gender!=null and gender!=''">
gender=#{gender},
if>
<if test="age!=null">
age=#{age},
if>
trim>
where id=#{id}
update>
TestMyBatis.java测试类
@Test
public void testUpdate1(){
Employee employee=new Employee();
employee.setId(5);
employee.setName("赵四");
employee.setGender("1");
int count = em.updateEmp1(employee);
System.out.println(count>0?"更新成功":"更新失败");
}
重点
EmployeeMapper.java接口
方式1:
//批量查询,查询ID是2或者4或者6的员工信息
List<Employee> selectEmps(int[] ids);
方式2:
//批量查询,查询ID是2或者4或者6的员工信息
List<Employee> selectEmps1(int[] ids);
EmployeeMapper.xml定义SQL语句
方式1:
select * from tb_employee where id in ( ? , ? , ? )
array:参数数组
<select id="selectEmps" resultType="Employee">
select * from tb_employee where id in
<foreach collection="array" open="(" item="id" separator="," close=")">
#{id}
foreach>
select>
方式2:
select * from tb_employee where id=? or id=? or id=?
array:参数数组
<select id="selectEmps1" resultType="Employee">
select * from tb_employee where
<foreach collection="array" item="a" separator="or">
id=#{a}
foreach>
select>
TestMyBatis.java测试类
@Test
public void testSelect2(){
int[] ids={2,4,6};
List<Employee> employees = em.selectEmps1(ids);
for (Employee employee : employees) {
System.out.println(employee);
}
}
EmployeeMapper.java接口
//批量删除
int deleteBatch(List<Integer> ids);
EmployeeMapper.xml定义SQL语句
<delete id="deleteBatch">
delete from tb_employee where id in
<foreach collection="list" open="(" item="id" separator="," close=")">
#{id}
foreach>
delete>
TestMyBatis.java测试类
@Test
public void testdeleteBatch(){
List<Integer> ids=new ArrayList<Integer>();
ids.add(1);
ids.add(3);
ids.add(5);
int count = em.deleteBatch(ids);
System.out.println(count);
}
EmployeeMapper.java接口
//批量新增
int addBatch(List<Employee> employees);
EmployeeMapper.xml定义SQL语句
list:参数集合
<insert id="addBatch">
insert into tb_employee values
<foreach collection="list" separator="," item="e">
(null,#{e.name},#{e.email},#{e.gender},#{e.age})
foreach>
insert>
TestMyBatis.java测试类
@Test
public void testAddaddBatch(){
List<Employee> employees=new ArrayList<Employee>();
employees.add(new Employee(null,"小琼1","[email protected]","0",18));
employees.add(new Employee(null,"小琼2","[email protected]","0",18));
employees.add(new Employee(null,"小琼3","[email protected]","0",18));
employees.add(new Employee(null,"小琼4","[email protected]","0",18));
employees.add(new Employee(null,"小琼5","[email protected]","0",18));
int count = em.addBatch(employees);
System.out.println(count);
}
封装服用思想:如果Maper.xml文件中有相同的SQL语句,那么没必要重复写,可以写成一个SQL片段,公用一下,减少代码量!
原始代码:
<select id="findEmpByNameAndGender" resultType="Employee">
select * from tb_employee where 1=1
<if test="name!=null and name!=''">
and name = #{name}
if>
<if test="gender!=null and gender!=''">
and gender = #{gender}
if>
select>
<select id="findEmpByNameAndGenders" resultType="Employee">
select * from tb_employee
<where>
<if test="name!=null and name!=''">
and name = #{name}
if>
<if test="gender!=null and gender!=''">
and gender = #{gender}
if>
where>
select>
封装复用后的代码:公有SQL片段
<!--公有SQL片段-->
<sql id="sqlFragment">
<if test="name!=null and name!=''">
and name = #{name}
</if>
<if test="gender!=null and gender!=''">
and gender = #{gender}
</if>
</sql>
<select id="findEmpByNameAndGender" resultType="Employee">
select * from tb_employee where 1=1
<include refid="sqlFragment"/>
</select>
<select id="findEmpByNameAndGenders" resultType="Employee">
select * from tb_employee
<where>
<include refid="sqlFragment"/>
</where>
</select>
@Before : 在**@Test测试方法之前**,自动优先运行@Before注解的方法
@Test : 单元测试
@After : 在**@Test测试方法之后**,自动运行@After注解的方法
package com.dream.test;
import com.dream.mapper.EmployeeMapper;
import com.dream.pojo.Employee;
import com.dream.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
public class TestMyBatis {
SqlSession sqlSession = null;
EmployeeMapper em = null;
@Before
public void init(){
sqlSession = MyBatisUtils.getSqlSession();
em = sqlSession.getMapper(EmployeeMapper.class);
}
@Test
public void testFindEmpByNameAndGender(){
Employee employee = new Employee();
employee.setName("Jerry");
employee.setGender("1");
List<Employee> employees = em.findEmpByNameAndGender(employee);
for (Employee e:employees) {
System.out.println(e);
}
}
@After
public void destroy(){
MyBatisUtils.closeSqlSession(sqlSession);
}
}
关联是双向的!
现实生活中实体和实体之间的关系:一对一 一对多 多对多
在现实的项目中进行数据库建模时,我们要遵循数据库设计范式的要求,会对现实中的业务模型进行拆分,封装在不同的数据表中,表与表之间存着一对一或一对多或是多对多的对应关系。进而,我们对数据库的增删改查操作的主体,也就从单表变成了多表。
MyBatis中是如何实现这种多表关系的映射呢?
答:用查询结果集标签****
resultMap元素是MyBatis中最重要最强大的元素。它就是让你远离90%的需要从结果集中取出数据的JDBC代码的那个东西,而且在一些情形下允许你做一些 JDBC 不支持的事情。
resultMap元素设计的初衷,就是简单语句不需要明确的结果映射,而很多复杂语句确实需要描述它们的关系。
resultMap元素中,允许有以下直接子元素:
:作用与result相同,同时可以标识出这个字段值可以区分其他对象实例,可以理解为数据表中的主键,可以定位数据表中唯一的记录。
:将数据表中的字段注入到Java对象属性中。
:关联,有一个关系,如"用户"有一个"账号",属性用javaType。
:集合,有很多关系,如"客户"有很多"订单",属性用ofType。
这些子元素所包含的属性:
property:表示实体类中定义的对象的名称。
column:表示数据库表中的字段名称。
javaType:association标签中使用的属性,代表一个关系。
oftype:collection标签中使用的属性,代表很多关系,集合。
补充:selcet元素中有很多属性,常见的如下:
id:命名空间唯一标识,可以被用来引用这条语句。
parameterType:该属性参数是传入类的完全限定名或者别名。
resultType:从这条语句要返回的期望类型的类的完全限定名或别名(这里应该注意的是集合的类型,应该是集合可以包含的类型,并不是集合本身)。
resultMap:引用外部的resultMap,其名称要和外部的resultMap元素的id名称一致,用于映射其结果到实体类指定对象中。
重要:resultType和resultMap不能同时使用。
面试题1:我们什么时候使用resultType?什么时候使用resultMap?
答:(1) 当去查询一张表时,可以使用resultType,这种情况下,MyBatis会在底层自动创建一个resultMap,基于属性名来映射到JavaBean属性上;
(2) 在使用resultMap时,就必须在xml文件中写resultMap;
一对一表查询
EmpMapper.java接口
//一对一表查询
//根据员工ID查询员工信息(包括该员工的工牌信息)
Emp findById(int id);
EmpMapper.xml定义SQL语句
这里取别名是为了防止column和property对应冲突
<select id="findById" resultMap="empMap">
SELECT
E.id 'eid',
E.name,
E.email,
E.gender,
E.age,
C.*
FROM tb_emp E INNER JOIN tb_card C ON E.id=C.emp_id
WHERE C.id=#{id}
select>
<resultMap id="empMap" type="Emp">
<id column="eid" property="id"/>
<result column="name" property="name"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
<result column="age" property="age"/>
<association property="card" javaType="Card">
<id column="id" property="id"/>
<result column="card_no" property="cardNo"/>
<result column="card_depart" property="cardDepart"/>
<result column="card_job" property="cardJob"/>
<result column="card_sal" property="cardSal"/>
<result column="emp_id" property="empId"/>
association>
resultMap>
TestEmp.java测试类
SqlSession sqlSession = null;
EmpMapper empMapper = null;
@Before
public void init(){
sqlSession = MyBatisUtils.getSqlSession();
empMapper = sqlSession.getMapper(EmpMapper.class);
}
@Test
public void testFindById(){
Emp emp = empMapper.findById(9);
System.out.println(emp);
}
@After
public void destroy(){
MyBatisUtils.closeSqlSession(sqlSession);
}
一对一反向表查询
EmpMapper.java接口
//一对一表反向查询
//根据员工的id查询工牌信息(包括该员工的员工信息)
Card findByCardId(int id);
EmpMapper.xml定义SQL语句
<select id="findByCardId" resultMap="cardMap">
SELECT
C.*,
E.id 'eid',
E.name,
E.email,
E.gender,
E.age
FROM tb_emp E INNER JOIN tb_card C ON E.id=C.emp_id
WHERE C.id=#{id}
select>
<resultMap id="cardMap" type="Card">
<id column="id" property="id"/>
<result column="card_no" property="cardNo"/>
<result column="card_depart" property="cardDepart"/>
<result column="card_job" property="cardJob"/>
<result column="card_sal" property="cardSal"/>
<result column="emp_id" property="empId"/>
<association property="emp" javaType="Emp">
<id column="eid" property="id"/>
<result column="name" property="name"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
<result column="age" property="age"/>
association>
resultMap>
TestEmp.java测试类
@Test
public void testFindByCardId(){
Card card = empMapper.findByCardId(3);
System.out.println(card);
}
一对多查询
编写实体类Movie
package com.dream.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Movie {
private Integer id;
private String movieTitle;
private String moviePrice;
private String movieTime;
private String movieActor;
private Integer typeId;
private MovieType movieType;
}
MovieMapper接口
//一对多查询
//查询电影信息(包含电影类别信息)
Movie findMovieById(int id);
MovieMapper.xml定义SQL语句
<select id="findMovieById" resultMap="movieMap">
SELECT
M.*,
T.id 'tid',
T.tname
FROM tb_type T INNER INTO tb_movie M ON T.ID=M.type_id
WHERE M.id=#{id}
select>
<resultMap id="movieMap" type="Movie">
<id column="id" property="id"/>
<result column="movie_title" property="movieTitle"/>
<result column="movie_price" property="moviePrice"/>
<result column="movie_time" property="movieTime"/>
<result column="movie_actor" property="movieActor"/>
<result column="type_id" property="typeId"/>
<association property="movieType" javaType="MovieType">
<id column="tid" property="id"/>
<result column="tname" property="tname"/>
association>
resultMap>
TestMovie.java测试类
SqlSession sqlSession = null;
MovieMapper movieMapper = null;
@Before
public void init(){
sqlSession = MyBatisUtils.getSqlSession();
movieMapper =sqlSession.getMapper(MovieMapper.class);
}
@Test
public void testFindMovieById(){
Movie movie = movieMapper.findMovieById(2);
System.out.println(movie);
}
@After
public void destroy(){
MyBatisUtils.closeSqlSession(sqlSession);
}
多对一查询
编写实体类MovieType
package com.dream.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MovieType {
private Integer id;
private String tname;
private List<Movie> movies;
}
MovieMapper接口
//多对一查询
//根据电影类别ID查询点类别信息(包括该类别下所有的电影信息)
MovieType findMovieType(int movieType_id);
MovieMapper.xml定义SQL语句
<select id="findMovieType" resultMap="movieTypeMap">
SELECT
T.id 'tid',
T.tname,
M.*
FROM tb_type T INNER JOIN tb_movie M ON T.id=M.type_id
WHERE T.id=3
select>
<resultMap id="movieTypeMap" type="MovieType">
<id column="tid" property="id"/>
<result column="tname" property="tname"/>
<collection property="movies" ofType="Movie">
<id column="id" property="id"/>
<result column="movie_title" property="movieTitle"/>
<result column="movie_price" property="moviePrice"/>
<result column="movie_time" property="movieTime"/>
<result column="movie_actor" property="movieActor"/>
<result column="type_id" property="typeId"/>
collection>
resultMap>
TestMovie.java测试类
@Test
public void testFindMovieType(){
MovieType movieType = movieTypeMapper.findMovieType(1);
System.out.println(movieType);
List<Movie> movies = movieType.getMovies();
for (Movie movie:movies) {
System.out.println(movie);
}
}
多对多查询
编写实体类Star
package com.dream.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Star {
private Integer id;
private String name;
private String age;
private String work;
private List<Role> roles;
}
StarMapper接口
//多对多查询
//查询明星信息(包括该明星的角色信息)
Star findById(int id);
StarMapper.xml定义SQL语句
<select id="findById" resultMap="starMap">
SELECT
S.*,
R.id 'rid',
R.role_name,
R.role_desc
FROM tb_star S INNER JOIN tb_star_role SR ON S.id=SR.star.id
INNER JOIN tb_role R ON SR.role_id=R.id
WHERE S.id=#{id}
select>
<resultMap id="starMap" type="Star">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
<result column="work" property="work"/>
<collection property="roles" ofType="Role">
<id column="rid" property="id"/>
<result column="role_name" property="roleName"/>
<result column="role_desc" property="roleDesc"/>
collection>
resultMap>
TestStar.java测试
package com.dream.test;
import com.dream.mapper.StarMapper;
import com.dream.pojo.Emp;
import com.dream.pojo.Role;
import com.dream.pojo.Star;
import com.dream.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class TestStar {
SqlSession sqlSession;
StarMapper starMapper;
@Before
public void init(){
sqlSession = MyBatisUtils.getSqlSession();
starMapper = sqlSession.getMapper(StarMapper.class);
}
@Test
public void testFindById(){
Star star = starMapper.findById(1);
System.out.println(star);
}
@After
public void destroy(){
MyBatisUtils.closeSqlSession(sqlSession);
}
}
反向多对多查询
编写实体类Role
package com.dream.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Role {
private Integer id;
private String roleName;
private String roleDesc;
private List<Star> stars;
}
StarMapper接口
//反向多对多查询
//查询角色信息(包含该角色的明星信息)
Role findByRoleId(int id);
StarMapper接口
<select id="findByRoleId" resultMap="roleMap">
SELECT
S.*,
R.id 'rid',
R.role_name,
R.role_desc
FROM tb_star S INNER JOIN tb_star_role SR ON S.id=SR.star_id
INNER JOIN tb_role R ON SR.role_id=R.id
WHERE R.id=#{id}
select>
<resultMap id="roleMap" type="role">
<id column="rid" property="id"/>
<result column="role_name" property="roleName"/>
<result column="role_desc" property="roleDesc"/>
<collection property="stars" ofType="Star">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
<result column="work" property="work"/>
collection>
resultMap>
TestStar.java测试
@Test
public void testFindRoleById(){
Role role = starMapper.findByRoleId(1);
System.out.println(role);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QwiLCkRq-1628923495030)(D:\Typora\图片\Cache缓存.png)]
- 缓存中有,先查询缓存。缓存中没有,那么查询数据库。这样的话不用每次都查询数据库。减轻数据库的压力。提高查询效率!!!
- 第一次查询的时候,由于缓存中没有,那么去查询数据库返回给客户端。同时还会把这个次查询的数据放入缓存。
- 第二次查询同样的数据时候,发现缓存中曾经有查询过的数据,那么直接从缓存中读取。不必再次查询数据库,减轻数据库服务器压力,缓存中有就查缓存,缓存中没有就查数据库!
- 如果数据库中数据发生了修改,那么缓存就会清空,保持数据的一致性!防止脏数据!
mybatis提供查询缓存,如果缓存中有数据就不用从数据库中获取,用于减轻数据压力,提高系统性能。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eBqCG43C-1628923495032)(D:\Typora\图片\MyBatis缓存.png)]
Mybatis的缓存,包括一级缓存和二级缓存。
一级缓存是sqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。一级缓存是默认使用的。
理解:一级缓存指的就是sqlSession,在sqlsession中有一个数据区域,是Map结构,这个区域就是一级缓存区域。一级缓存中的key是由sql语句、条件、statement等信息组成的一个唯一值;一级缓存中的value,就是查询出的结果对象。
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。二级缓存需要配置,然后手动开启。
理解:二级缓存指的就是同一个namespace下的mapper,二级缓存中,也有一个Map结构,这个区域就是二级缓存区域。二级缓存中的key是由sql语句、条件、statement等信息组成的一个唯一值;二级缓存中的value,就是查询出的结果对象。
Map
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n8f3n3vH-1628923495033)(D:\Typora\图片\一级缓存示意图.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UpGLK0yq-1628923495034)(D:\Typora\图片\一级缓存流程图.png)]
第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。
如果sqlSession去执行commit(执行插入、更新、删除),或者手动清除缓存(sqlSession.clearCache()),清空sqlSession中的一级缓存,这样做的目的是为了让缓存中存储的是新信息,避免脏读,避免缓存的一致性问题。
第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。
测试:
String resource = "mybatis-config.xml"
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
Book book01 = bookMapper.findById(1);第一次查询,先查询数据库,数据库查询到之后 再放入一级缓存中
System.out.println(book01);
//手动清除缓存
//sqlSession.clearCache();
//调用了增删改,那么缓存也会自动清空
//Book book = new Book();
//book.setId(2);
//book.setTitle("斗罗大陆2");
//int i = bookMapper.updateBook(book);
//System.out.println("更新成功!");
Book book02 = bookMapper.findById(1);//第二次查询同样的数据,那么直接查询缓存
System.out.println(book02);
sqlSession.close();
System.out.println(book01==book02);
一级缓存是默认自动打开的。
关闭一级缓存:
方式一:在核心配置文件mybatis-config.xml中加入以下内容(开启一级缓存总开关),在settings标签中添加以下内容:
<settings>
<setting name="localCacheScope" value="STATEMENT"/>
settings>
方式二:
在StudentMapper.xml映射文件中,加入以下内容(局部开启一级缓存)
开启一级缓存:flushCache=“false”(默认值)
<select id="findById" resultType="Book" flushCache="false" useCache="true">
select * from tb_book where id=#{id}
select>
关闭一级缓存:flushCache=“true” 局部开关:刷新缓存,也就是关闭一级缓存!
<select id="findById" resultType="Book" flushCache="true" useCache="true">
select * from tb_book where id=#{id}
select>
补充:
当为select语句时:
flushCache默认为false,表示任何时候语句被调用,都不会去清空本地缓存和二级缓存;
useCache默认为true,表示会将本条语句的结果进行二级缓存。
当为insert、update、delete语句时:
flushCache默认为true,表示任何时候语句被调用,都会导致本地缓存和二级缓存被清空;
useCache属性在该情况下没有。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7hsDm4wj-1628923495036)(D:\Typora\图片\二级缓存示意图.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PFsYkXtS-1628923495037)(D:\Typora\图片\二级缓存流程图.png)]
第一次调用mapper下的SQL去查询用户信息。查询到的信息会存到该mapper对应的二级缓存区域内。
第二次调用相同namespace下的mapper映射文件(xml)中相同的SQL去查询用户信息,会去对应的二级缓存内取结果。
如果调用相同namespace下的mapper映射文件的增删改(SQL),并执行了commit操作,此时会清空该namespace下的二级缓存。
测试:
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession01 = sqlSessionFactory.openSession(true);
BookMapper bookMapper01 = sqlSession01.getMapper(BookMapper.class);
Book book01 = bookMapper01.findById(1);
System.out.println(book01);
//sqlSession1需要此处关闭,因为sqlSession关闭,那么就放入二级缓存中
sqlSession01.close();
System.out.println("-----------------------------------------------");
//中途执行增删改,二级缓存是否会失效(清空)
//SqlSession sqlSession03 = sqlSessionFactory.openSession(true);
//BookMapper bookMapper03 = sqlSession03.getMapper(BookMapper.class);
//Book book = new Book();
//book.setId(1);
//book.setTitle("斗罗大陆2");
//bookMapper03.updateBook(book);
//sqlSession03.close();
SqlSession sqlSession02 = sqlSessionFactory.openSession(true);
BookMapper bookMapper02 = sqlSession02.getMapper(BookMapper.class);
Book book02 = bookMapper02.findById(1);
System.out.println(book02);
sqlSession02.close();
System.out.println(book01==book02);
如果第一个session没有提交,则没有进行二级缓存。所以,想实现二级缓存,需要前面的session已经提交过,并且相同的提交sql。
开启二级缓存:
MyBatis默认是没有开启二级缓存的。
方式一:在核心配置文件mybatis-config.xml中加入以下内容(开启二级缓存总开关),在settings标签中添加以下内容:
<settings>
<setting name="cacheEnabled" value="true"/>
settings>
方式二:在StudentMapper.xml映射文件中,加入以下内容(局部开启二级缓存),开启二级缓存:
<cache>cache>
设置cache标签的属性:
- eviction:缓存回收策略,默认值是LRU,有以下几种回收策略:
- LRU:最近最少回收,移除最长时间不被使用的对象
- FIFO:先进先出,按照缓存进入的顺序来移除它们
- SOFT:软使用,移除基于垃圾回收器状态和软引用规则的对象
- WEAK:弱使用,更积极的移除基于垃圾回收器和弱引用规则的对象
- flushinterval:缓存刷新间隔,缓存多长时间刷新一次,设置一个毫秒值,默认是不刷新的
- readOnly:是否只读;
- true:只读。MyBatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据MyBatis为了加快获取数据,直接就会将数据在缓存中的引用交给用户;不安全,速度快
- false:读写(默认)。MyBatis认为不仅会有读取数据,还会有修改操作。会通过序列化和反序列化的技术克隆一份新的数据给用户
- size:缓存中的对象个数
- type:指定自定义缓存的全类名(实现Cache接口即可),自定义缓存或者整合第三方缓存时使用
- blocking:若缓存中找不到对应的key,缓存会一直blocking,直到有对应的数据进入内存
补充:
由于二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,所以需要给缓存的对象执行序列化。
缓存默认是存入内存中,但如果需要把缓存对象存入硬盘那么就需要序列化(实体类要实现)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ga1qio37-1628923495038)(D:\Typora\图片\自动实现序列化.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XKdFjRqc-1628923495039)(D:\Typora\图片\序列化ID添加.png)]
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book implements Serializable {
private static final long serialVersionUID = 2344697603915147867L;
private Integer id;
private String title;
private Double price;
private String publish;
}
如果该类存在父类,那么父类也要实现序列化
禁用二级缓存:
该statement中设置userCache=“false”,可以禁用当前select语句的二级缓存,即每次查询都是去数据库中查询,默认情况下是true,即该statement使用二级缓存。
<select id="findById" resultType="Book" flushCache="false" useCache="false">
select * from tb_book where id=#{id}
select>