配置解析
1.核心配置
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息
configuration(配置)
-
environments(环境配置)
-
environment(环境变量)
-
transactionManager(事务管理器)
-
dataSource(数据源)
-
-
2.环境配置(environments)
在xml中,所有的标签都有其顺序,不可打乱
properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?
MyBatis 可以配置成适应多种环境
尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
每个数据库对应一个 SqlSessionFactory 实例
<environments default="development">
<environment id="development">
<!--transactionManager事务管理,默认为JDBC,共有两种,另一种为MANAGED,不常用-->
<transactionManager type="JDBC"/>
<!--有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]")
没有连接池/有连接池/正常连接
-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<!--jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8-->
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
3.属性(properties)
这些属性可以在外部进行配置,并可以进行动态替换
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
password=123
<properties resource="db.properties"/>
<!-- 也能额外添加一些属性,优先使用外部配置文件里面的属性-->
<properties><property name="username" value="root"/>
<property name="password" value="123"/>
</properties>
4.类型别名(typeAliases)
类型别名可为 Java 类型设置一个缩写名字, 意在降低冗余的全限定类名书写
<!--可以给实体类自定义别名-->
<typeAliases>
<typeAlias type="com.liu.entity.User" alias="User"/>
</typeAliases>
通过包来取别名,默认为这个类的类名,不分大小写,建议用小写
<typeAliases>
<package name="com.liu.entity"/>
</typeAliases>
可以通过注解来更改别名,不建议使用这种方式
@Alias("test")
public interface UserDao {
5.映射器(mappers)
<!--每一个Mapper.xml文件都要在MyBatis核心配置文件中注册-->
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="com/liu/dao/UserMapper.xml"/>
</mappers>
<!--使用映射器接口实现类的完全限定类名-->
<!-- 注意:接口和他的配置文件必须同名-->
<!--接口和他的配置文件必须在同一包下-->
<!-- 使用映射器接口实现类的完全限定类名 -->
<mapper class="com.liu.dao.UserMapper"/>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
包注入同上
<package name="com.liu.dao"/>
6.作用域(Scope)和生命周期
作用域、生命周期类别是童年的,因为错误的使用会导致非常严重的并发问题。
SqlSessionFactoryBuilder:
-
创建创作了SqlSessionFactory,就不再需要它了,因此SqlSessionFactoryBuilder实例的最佳作用是作用域(也就是局部方法方法)。
SqlSessionFactory:
-
数据库连接池
-
SqlSession 应用游戏被创建就应该在运行整个过程中一直存在,没有任何环境破坏或创建另一个实例。
-
因此SqlSessionFactory 的最佳作用域是应用作用域。 04. 最简单的就是使用单例模式或者静态单例模式。
SqlSession:
-
连接到连接池的一个请求;
-
每个线程都应该有它自己的SqlSession实例。SqlSession的实例不是线程安全的,因此是不能被共享的,所以它的最佳作用是域或方法作用域。绝对不能将SqlSession实例的引用放出在一个类的动态域,甚至一个类的实例变量也不行。
-
用完后需要关闭,否则资源被占用。
try (SqlSession session = sqlSessionFactory.openSession()) { // 你的应用逻辑代码 }
在所有代码中都遵循这种使用模式,可以保证所有数据库资源都被正确地关闭。
这里的每个Mapper,就代表一个具体的业务!
分页
使用Limit分页
使用Mybatis实现分页,核心是SQL
语法:select * from mybatis.user limit indexpage,pageSize
select * from mybatis.user limit 3 【0,3】
实现
1.在接口内编写方法,使用map传递参数
List<User> getLimit(Map<Object,Object> map);
2.实现方法
<!--结果集映射 解决实体类属性和数据库字段不相同的问题-->
<resultMap id="UserMap" type="User">
<!--column代表数据库中的字段 property代表实体类中的属性-->
<result column="pwd" property="password"/>
</resultMap>
<select id="getLimit" parameterType="map" resultMap="UserMap">
select * from mybatis.user limit #{startIndex},#{PageSize}
</select>
3.测试代码
@Test
public void Limit(){
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<Object, Object> map = new HashMap<Object, Object>();
map.put("startIndex",0);
map.put("PageSize",2);
List<User> limit = mapper.getLimit(map);
for (User user: limit){
System.out.println(user);
}
sqlSession.close();
}
RowBounds分页
不再使用SQL实现分页
接口
//使用RowRounds实现分页
List<User> getRowBounds();
实现
<select id="getRowBounds" resultMap="UserMap">
select * from mybatis.user
</select>
测试
@Test
public void getRowBounds(){
SqlSession sqlSession = MyBatisUtil.getSqlSession();
//RowBounds实现
RowBounds rowBounds = new RowBounds(0, 2);
//基于java代码实现分页
List<User> us = sqlSession.selectList("com.liu.dao.UserMapper.getRowBounds", null, rowBounds);
for (User user:us){
System.out.println(user);
}
sqlSession.close();
}
使用注解开发
CRUD
1.注解在接口上实现
@Select("select * from mybatis.user")
List<User> gtelist();
//方法存在多个参数,所有参数前面必须加上@Param注解
@Select("select * from mybatis.user where id = #{id}")
User ById(@Param("id") int id);
@Insert("insert into mybatis.user(id,username,pwd) values(#{id},#{username},#{pwd})")
int add(User user);
@Delete("delete from mybatis.user where id = #{id}")
int del(@Param("id") int id);
@Update("update mybatis.user set username=#{username},pwd=#{pwd} where id=#{id}")
int upd(User user);
关于@Param()注解
基本类型的参数或者String类型的参数需要加上
引用类型不需要加
如果只有一个基本类型的话,可以忽略,但是建议加上
我们在Sql中引用的,就是@param中设置的属性名
2.需要在核心配置文件中绑定接口
注意:我们必须要将接口绑定到我们的核心配置文件中
<mappers>
<mapper class="com.liu.dao.UserMapper"/>
</mappers>
3.测试
public void list(){
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//查询
List<User> gtelist = mapper.gtelist();
for (User user : gtelist) {
System.out.println(user);
}
//按ID查询
User user = mapper.ById(2);
System.out.println(user);
//修改
mapper.upd(new User(9,"周怡","666999"));
//删除
mapper.del(10);
//添加
mapper.add(new User(10,"周怡","666"));
sqlSession.close();
}
本质:反射机制实现
底层:动态代理
public static SqlSession getSqlSession(){
//开启自动提交事务
return sqlSessionFactory.openSession(true);
}
Lombok
Project Lombok是一个java库,它可以自动插入编辑器和构建工具,为java增添乐趣。永远不要再编写另一个getter或equals方法,只要有一个注释,您的类就有一个功能齐全的构建器、自动记录变量等等。
使用步骤
1.在IDEA中安装Lombok插件
2.在项目中导入Lombok的jar包
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
3在实体类上加注解
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
private int id;
private String username;
private String pwd;
}
@Getter and @Setter//
@FieldNameConstants //字段,属性,常量
@ToString//
@EqualsAndHashCode//
@AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor//
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Data //
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows
@val
@var
experimental @var
@UtilityClass
Lombok config system
Code inspections
Refactoring actions (lombok and delombok)
@Data:无参构造,get,set,hashcode,tostring,equals
@AllArgsConstructor:有参构造
@NoArgsConstructor:无参构造
多对一
多个学生,对应一个老师
对学生而言,关联,多个学生,关联一个老师【多对一】
对老师而言,集合,一个老师,又很多学生,【一对多】
CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO teacher(`id`, `name`) VALUES (1, '秦老师');
teacher
CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8INSERT INTO `student` (`id`, `name`, `tid`) VALUES (1, 小明, 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (2, '小红', 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (3, '小张', 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (4, '小李', 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (5, '小王', 1);
Mybatis核心配置中结果映射(resultMap)
测试环境搭建
1.导入Lombok
2.新建实体类
3.建立Mapper接口
4.建立Mapper.xml文件
5.在核心配置文件中绑定注册我们的Mapper或者xml文件
6.测试查询是否成功
按照查询嵌套处理
实体
private int id;
private String name;
//多个学生关联一个老师
private Teacher teacher;
接口
List<Student> getStudent();
实现
<!--
思路:
1.先查出所有的学生信息
2.根据查询出来的学生的tid,查询对应的老师,子查询
-->
<select id="getStudent" resultMap="StudentTeacher">
select * from mybatis.student
</select>
<resultMap id="StudentTeacher" type="Student">
<!--result只能处理简单的属性-->
<result property="id" column="id"/>
<result property="name" column="name"/>
<!--association用来处理复杂的属性 对象使用:association 集合使用:collection -->
<association property="teacher" column="tid" javaType="teacher" select="getById"/>
</resultMap>
<select id="getById" resultType="teacher">
select * from mybatis.teacher where id =#{id}
</select>
按照结果嵌套处理
实现
<!--给SQL语句中查询的字段取别名-->
<select id="getStudent2" resultMap="TeacherName">
SELECT s.id sid,s.name sname,t.name tname FROM mybatis.student s,teacher t WHERE s.tid=t.id
</select>
<resultMap id="TeacherName" type="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="teacher">
<result property="name" column="tname"/>
</association>
</resultMap
回顾多对一查询方式
-
子查询
-
联表查询
一对多
按照结果嵌套处理
实体类
private int id;
private String name;
//一个老师拥有多个学生
private List<Student> students;
接口
Teacher getTeacher(@Param("tid") int id);
实现
<!--按结果嵌套查询-->
<select id="getTeacher" resultMap="st">
SELECT s.id sid,s.name sname,t.name tname,t.id tid FROM student s,teacher t WHERE s.tid=t.id and t.id=#{tid}
</select>
<resultMap id="st" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!-- association用来处理复杂的属性 对象使用:association 集合使用:collection -->
<!-- javaType: 指定属性的类型 集合中的泛型信息用ofType获取-->
<collection property="students"ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
按照查询嵌套处理
实体和方法不变
-
关联 - association 多对一
-
集合 - collection 一对多
-
javaType & ofTyp
-
javaType用来指定实体类中属性的类
-
ofType用来指定映射到List 或者集合中的pojo类型,泛型中的约束类型
实现
<select id="getST" resultMap="tt">
select * from mybatis.teacher where id=#{tid}
</select>
<resultMap id="tt" type="Teacher">
<collection property="students" javaType="ArrayList" ofType="Student" select="getStudentTeacher" column="id">
</collection>
</resultMap>
<select id="getStudentTeacher" resultType="Student">
SELECT * FROM student WHERE tid=#{tid}
</select>
动态SQL
什么是动态SQL:动态SQL就是指根据不同的条件生成不同的SQL语句
环境搭建
CREATE TABLE `blog`(
`id` VARCHAR(50) NOT NULL COMMENT 博客id,
`title` VARCHAR(100) NOT NULL COMMENT 博客标题,
`author` VARCHAR(30) NOT NULL COMMENT 博客作者,
`create_time` DATETIME NOT NULL COMMENT 创建时间,
`views` INT(30) NOT NULL COMMENT 浏览量
)ENGINE=INNODB DEFAULT CHARSET=utf8
实体
import java.util.Date;
@Data
public class Blog {
private String id;
private String title;
private String author;
private Date createTime;//属性名和字段名不一致create_time
private int views;
}
<!-- 是否开启驼峰命名自动映射 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
ID工具类
public class IDUtils {
public static String getID(){
return UUID.randomUUID().toString().replaceAll("-","");
}
IF(test)
接口
List<Blog> queryBlog(Map map);
实现
<select id="queryBlog" parameterType="map" resultType="Blog">
SELECT * FROM blog
<where>
<if test="title != null">
title like #{title}
</if>
<if test="author">
and author like #{author}
</if>
</where>
</select>
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
测试
@Test
public void query(){
SqlSession sqlSession = MyBatisUtil.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
//map.put("title","CRUD");
map.put("author","%十%");
mapper.queryBlog(map);
sqlSession.close();
}
Choos(when、otherwise)
<select id="query" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<choose>
<when test="title != null">
title =#{title}
</when>
<when test="author != null">
and author=#{author}
</when>
<otherwise>
views=#{views}
</otherwise>
</choose>
</where>
</select>
Update( set,if,test )
使用Map传参
<update id="updBlog" parameterType="map">
update mybatis.blog
<set>
<if test="title != null">
title=#{title},
</if>
<if test="author !=null">
author=#{author}
</if>
</set>
where id=#{id}
</update>
trim
指定前缀,去除AND和OR
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
指定后缀,去除逗号
<trim prefix="SET" suffixOverrides=",">
...
</trim>
所谓的动态SQL,本质上还是SQL语句,只是我们可以在SQL层面,去执行一个逻辑代码
Foreach
格式
item 集合项
index 索引
collection 指定需要遍历的集合
open 拼接头的字符串
close 拼接尾的字符串
separator 分隔符
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
案例
SQL语句
select * from mybatis.blog where (id=1 or id=2 or id=3)
接口
List<Blog> BlogForeach(Map map);
实现
<select id="BlogForeach" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<foreach collection="list" item="id" open="(" close=")" separator="or">
id=#{id}
</foreach>
</where>
</select>
测试
public void Foreach(){
SqlSession sqlSession = MyBatisUtil.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
ArrayList<Object> list = new ArrayList<Object>();
list.add(2);
map.put("list",list);
List<Blog> blogs = mapper.BlogForeach(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
SQL片段
有时候,我们会将一部分一些功能的部分抽取出来,方便复用
1.使用SQL标签抽取需要复用的部分
<sql id="query-choose">
<choose>
<when test="title != null">
title =#{title}
</when>
<when test="author != null">
and author=#{author}
</when>
<otherwise>
views=#{views}
</otherwise>
</choose>
</sql>
2.在需要使用的地方使用include标签引用即可
<select id="query" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<include refid="query-choose"></include>
</where>
</select>
主要事项
-
最好基于单标来定义SQL片段
-
不要存在where标签
动态SQL就是在拼接SQL语句,我们只要保证SQL的正确性,按照SQL的格式,去排列组合就可以了
缓存
简介
-
查询:连接数据库,秏资源
-
一次查询的结果,给它暂存到可以直接取到的地方---->内存:缓存
-
我们再次查询相同数据的时候,直接走缓存,就不用走数据库了
1.什么是缓存?
-
存在内存中的临时数据
-
将用户经常查询的数据放在缓存中,用户去查询数据就不用从数据库中查询,而是从缓存中查询,提高查询效率,解决高并发系统的性能问题
2.为什么使用缓存?
-
减少和数据库的交互次数,减少系统开销,提高系统效率
3.什么样的数据能使用缓存?
-
经常查询并且不经常改变的数据
MyBatis缓存
MyBatis包含了一个非常强大的查询缓存特性,它可以非常方便的定制和配置缓存,缓存可以极大的提升查询效率
MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
-
默认情况下,只有一级缓存开启(sqlsession级别的缓存,也称为本地缓存)
-
二级缓存需要手动开启和配置,它是基于namespace级别的缓存
-
为了提高扩展性,MyBatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义二级缓存
一级缓存
一级缓存也叫本地缓存:sqlsession
-
与数据库同一次会话期间查询到的数据会放在本地缓存中
-
以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库
测试
-
开启日志
-
测试再一次session中查询两次相同的记录
@Test public void query(){ SqlSession sqlSession = MyBatisUtil.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.queryById(1); System.out.println(user); System.out.println("==============================="); User user1 = mapper.queryById(1); System.out.println(user1); sqlSession.close(); }
-
查看日志输出:SQL语句只走了一次,第二次的数据是直接从Sqlsession中获取的
缓存失效的情况
-
查询不同的数据
-
增删改操作,可能会改变原来的数据,所以必定会刷新缓存
-
-
查询不同的Mapper
-
手动清理缓存
sqlSession.clearCache();//手动清理缓存
小结:一级缓存默认开启,只在一次sqlsession中有效,也就是拿到连接到关闭连接这个区间有效
二级缓存
-
二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
-
基于namespace级别的缓存,一个名称空间,对应一个二级缓存
工作机制
-
一个会话查询一条数据,这条数据就会被放在当前会话的一级缓存中
-
如果当前会话关闭了,这个会话对应的一级缓存就没了,但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中
-
新的会话查询信息,就可以从二级缓存中获取内容
-
不同的Mapper查出的数据会放在自己对应的缓存(map)中
步骤:
-
开启全局缓存
<!-- 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。--> <setting name="cacheEnabled" value="true"/>
-
在要使用二级缓存的Mapper中开启二级缓存
<!--开启二级缓存--> <cache/>
也可以在里面自定义一些参数
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
-
测试
public void Two(){ //创建两个sqlsession对象,执行同一个方法查询同一条数据,当第一个sqlsession缓存关闭的时候 //一级缓存中的数据会被保存到二级缓存中,下一个方法可以直接从缓存中获取数据 SqlSession sqlSession = MyBatisUtil.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.queryById(9); System.out.println(user); sqlSession.close(); SqlSession sqlSession2 = MyBatisUtil.getSqlSession(); UserMapper mapper2= sqlSession2.getMapper(UserMapper.class); User user2 = mapper2.queryById(9); System.out.println(user2); System.out.println(user==user2); sqlSession2.close(); }
序列化实体类,有时候会报错
public class User implements Serializable
小结:
-
只要开启了二级缓存,在同一个Mapper下就有效
-
所有的数据都会先放在一级缓存中
-
只有当会话提交,或者关闭的时候,才回提交到二级缓存中去
第一次写博客,随便写写,记录一下之前学习MyBatis时候的笔记,还有一点稍候补上