概念
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
接口的实现类由原来的Impl转变为了一个Mapper配置文件。
即:
public class StudentDaoImpl implements StudentDao{
public List<Student> getStudent() {
//1、获取Class类
//2、获取数据库链接
//3、写sql
//4、获取Statement或者PrepareStatement
//5、执行sql,获取结果
return null;
}
}
优化为一个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接口,id为所对应接口的方法,reslutType代表返回值类型-->
<mapper namespace="com.xia.dao.StudentDao">
<select id="getStudent" resultType="com.kuang.pojo.Student">
select * from school.student where id = #{id}
</select>
</mapper>
调用查询工具
public class MybatisUtils {
/*1、获取Class类
2、获取数据库链接
替换为了
1、获取SqlSessionFactory*/
private static SqlSessionFactory sqlSessionFactory;
static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
用法总结:
1、写工具类,为了所有的请求查询方法共用,优化代码,减少代码量
2、配置数据库链接资源文件,包含数据库ip,实例,账号,密码
3、写接口所对应的Mapper.xml(其实是Impl的马甲化身)
4、把Mapper注册到数据库配置文件中,通常是com/xia/
Mapper传递参数
每一个Mapper代表一个业务
public interface StudentDao {
List<Student> getStudent();
Student getStudentById(int StudentId);
int addStudent(Student student);
//map中键的名称与配置中sql中的参数保持一致即可传递成功
int updateStudentById(Map<String,Object> map);
int deleteStudent(int StudentId);
}
<update id="updateStudentById" parameterType="map">
update school.student set age = #{age} where id=#{studentId}
</update>
测试类
@Test
public void updateStudent(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
Map<String,Object> map = new HashMap<String, Object>();
map.put("age",29);
map.put("studentId",1);
mapper.updateStudentById(map);
sqlSession.commit();
sqlSession.close();
}
Map注册的三种方法
<mappers>
<!-- 一、 <mapper resource="com/xia/dao/StudentMapper.xml"/>-->
<!-- 二、 <package name="com.xia.dao"/> -->
<!-- 三、 <mapper class="comxia.dao.StudentMapper"></mapper>-->
</mappers>
tip:第二种和第三种方法都必须让Mapper和Mapper.xml名字保持一致
日志工厂类
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
日志内容:
标准日志工厂:STDOUT_LOGGING
LOG4J配置项:
log4j.rootLogger = DEBUG,Console,File
### 输出DEBUG 级别以上的日志到=控制台 ###
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 = %-d{yyyy-MM-dd HH:mm:ss} - [ %p ] %l %c %t - %m %n
### 输出到日志文件 ###
log4j.appender.File=org.apache.log4j.RollingFileAppender
log4j.appender.File.File=${project}src\\main\\resources\\app.log
log4j.appender.File.MaxFileSize=10MB
log4j.appender.File.Threshold=ALL
log4j.appender.File.layout=org.apache.log4j.PatternLayout
log4j.appender.File.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH\:mm\:ss,SSS}][%c]%m%n
### 输出DEBUG 级别以上的日志到=E://logs/error.log ###
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = ${project}src\\main\\resources\\app.log
log4j.appender.D.Append = true
log4j.appender.D.Encoding=UTF8
log4j.appender.D.Threshold = DEBUG
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} - [ %p ] %l %c %t - %m %n
### 输出ERROR 级别以上的日志到=E://logs/error.log ###
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File =${project}src\\main\\resources\\app.log
log4j.appender.E.Append = true
log4j.appender.E.Encoding=UTF8
log4j.appender.E.Threshold = ERROR
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} - [ %p ] %l %c %t - %m %n
复杂查询(使用Mybatis实现关联查询)
其根本就是实体与结果集的映射
数据库表设计:
CREATE TABLE `Teacher` (
`id` int(11) unsigned NOT NULL COMMENT '老师Id',
`name` varchar(100) DEFAULT NULL COMMENT '老师姓名',
`age` int(11) DEFAULT NULL COMMENT '年龄'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `student` (
`id` int(11) NOT NULL COMMENT '学员Id',
`name` varchar(100) DEFAULT NULL COMMENT '学生姓名',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`tid` bigint(20) DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
① 一对多(多个学生关联一个老师):
查询实体中有另外一个Java类(如本例中学生中的Teacher),需要将另外一个实体类信息引到查询结果中。
select s.id sid,s.name sname,s.age sage,t.name tname,t.age tage from school.student s,school.Teacher t where s.tid = t.id
类设计
package com.xia.dao;
import com.xia.pojo.Student;
import java.util.List;
public interface StudentMapper {
List<Student> getStudentAndTeacher();
}
package com.xia.pojo;
import lombok.*;
@ToString
@Setter
@Getter
@NoArgsConstructor
@EqualsAndHashCode
public class Student {
private int id;
private String name;
private int age;
private Teacher teacher;
}
package com.xia.pojo;
import lombok.*;
@ToString
@Setter
@Getter
@NoArgsConstructor
@EqualsAndHashCode
public class Teacher {
private int id;
private String name;
private int age;
}
拆分成两个查询
~xml
<select id="getStudentAndTeacher" resultMap="StudentAndTeacher2">
select * from school.student
</select>
<resultMap id="StudentAndTeacher2" type="Student">
<association property="teacehr" column="tid" javaType="Teacher" select="getTeacher">
</association>
</resultMap>
<select id="getTeacher" resultType="Teacher">
select * from school.Teacher where id = #{id}
</select>
关联查询
<mapper namespace="com.xia.dao.StudentMapper">
<resultMap id="StudentAndTeacher" type="Student" >
<result column="sid" property="id"/>
<result column="sname" property="name"/>
<result column="sage" property="age"/>
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
<result property="age" column="tage"/>
</association>
</resultMap>
<select id="getStudentAndTeacher" resultMap="StudentAndTeacher">
select s.id sid,s.name sname,s.age sage,t.name tname,t.age tage from school.student s,school.Teacher t where s.tid = t.id;
</select>
测试类
@Test
public void getSAndT(){
/*
1、查询出所有学生的ID
2、根据学生的ID查找DUIYINGDE LAOSHI
*/
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> userAndTeacher1 = mapper.getStudentAndTeacher();
for (Student userAndTeacher :userAndTeacher1
) {
System.out.println(userAndTeacher);
}
}
2、多对一(一个老师包含多个学生)
如本例中,老师实体中包含多个学生。(查询实体中包含List)
select s.id sid,s.name sname,s.age sage,t.name tname,t.age tage from school.student s,school.Teacher t where s.tid = t.id**
类设计
import lombok.*;
/**
* @author Guangpei Xia
* @create 2021-06-14-15:58
*/
@ToString
@Setter
@Getter
@NoArgsConstructor
@EqualsAndHashCode
public class Student {
private int id;
private String name;
private int age;
private int tid;
}
import lombok.*;
@ToString
@Setter
@Getter
@NoArgsConstructor
@EqualsAndHashCode
public class Teacher {
private int id;
private String name;
private int age;
private List<Student> students;
}
/**
* @author Guangpei Xia
* @create 2021-06-19-19:30
*/
public interface TeacherMapper {
List<Teacher> getTeacher();
Teacher getTeacherAndStudent(@Param("tid") int id);
Teacher getTeacherAndStudent2(@Param("tid") int id);
}
拆分成两个查询
<!--from dao where function name = 'getTeacherAndStudent2'-->
<select id="getTeacherAndStudent2" resultMap="TeacherAndStudent2">
select * from school.teacher where id = #{tid}
</select>
<!--binding the resultMap of function-->
<resultMap id="TeacherAndStudent2" type="Teacher">
<!--结果集的一个扩展映射,通过select标签扩展子查询 -->
<collection property="students" javaType="ArrayList" ofType="Student" select="selectStudentOfTeacher" column="id"/>
</resultMap>
<select id="selectStudentOfTeacher" resultType="Student">
select * from school.student where tid = #{tid}
</select>
关联查询
<select id="getTeacherAndStudent" resultMap="TeacherAndStudent">
select t.id tid,t.name tname,t.age tage,s.id sid,s.name sname from school.teacher t left join school.student s on t.id = s.tid where t.id = #{tid}
</select>
<resultMap id="TeacherAndStudent" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<result property="age" column="tage"/>
<collection property="students" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
</collection>
</resultMap>
测试类
@Test
public void getTeacherAndStudent(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
TeacherMapper teacherMapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacherAndStudent = teacherMapper.getTeacherAndStudent(1);
System.out.println(teacherAndStudent);
}
@Test
public void getTeacherAndStudent2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacherAndStudent2 = mapper.getTeacherAndStudent2(1);
System.out.println(teacherAndStudent2);
}
全局配置项
<?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>
<!-- <settings>-->
<!-- <setting name="logImpl" value="LOG4J"/>-->
<!-- </settings>-->
<typeAliases>
<typeAlias type="com.xia.pojo.User" alias="User"></typeAlias>
<typeAlias type="com.xia.pojo.Teacher" alias="Teacher"></typeAlias>
<typeAlias type="com.xia.pojo.Student" alias="Student"></typeAlias>
</typeAliases>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/school"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- <mapper resource="com/com.xia/dao/StudentMapper.xml"/>-->
<package name="com.xia.dao"/>
<!-- <mapper class="comxia.StudentMapper"></mapper>-->
</mappers>
<!-- ?useSSL=TRUE&useUnicode=true&chacterEncoding=utf-8-->
</configuration>
面试高频:
Mysql引擎
InnoDB底层原理
索引
索引优化
总结
javaType:指定实体类pojo中的属性
ofType:指定实体类中List或者集合中pojo类型。可以理解为javatype的明细项。本例一种实体只有一个student,用javatype映射类。本例二种,实体有List项,List的的泛型为Student。
动态SQL
DATABASE:
create table school.`blog`(
`id` BIGINT (20) UNSIGNED NOT NULL AUTO_INCREMENT,
`title` VARCHAR(100) NOT NULL COMMENT '标题',
`author` VARCHAR(30) NOT NULL COMMENT '作者',
`password` VARCHAR(100) NOT NULL COMMENT '密码',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP ,
`view` INT (30) UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COMMENT='博客';
类:
import lombok.*;
import java.util.Date;
/**
* @author Guangpei Xia
* @create 2021-06-20-12:12
*/
@ToString
@Setter
@Getter
@AllArgsConstructor
@EqualsAndHashCode
public class Blog {
private String id;
private String title;
private String author;
private String password;
private Date create_time;
private int view;
}
package com.xia.dao;
import com.xia.pojo.Blog;
import java.util.List;
import java.util.Map;
/**
* @author Guangpei Xia
* @create 2021-06-20-12:16
*/
public interface BlogMapper {
int addBlog(Blog blog);
List<Blog> queryBlogIf(Map map);
}
配置项:
<!--1 if-->
<select id="queryBlogIf" parameterType="map" resultType="Blog">
select * from school.blog
<where>
<if test="id != null">
id = #{id}
</if>
<if test="author != null">
and author like #{author}
</if>
</where>
</select>
<!--2 choose when-->
<select id="queryBlogChoose" parameterType="map" resultType="Blog">
select * from school.blog
<where>
<choose>
<when test="id != null">
id = #{id}
</when>
<when test="author != null and password !=null">
and (password = #{password} and author = #{author})
</when>
</choose>
</where>
</select>
<!--3 set-->
<update id="updateSet" parameterType="map">
update school.blog
<set>
<if test="title !=null">
title = #{title},
</if>
<if test="author != null">
author = #{author},
</if>
<if test="view != null">
view = #{view}
</if>
</set>
<where>
id=#{id}
</where>
</update>
测试类:
@Test
public void queryByIf(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map map = new HashMap<String,String>();
map.put("author","%"+"xia"+"%");
List<Blog> blogs = mapper.queryBlogIf(map);
for (Blog b:
blogs) {
System.out.println(blogs);
}
}
sql引用
上例中的mapper中的update配置可以改写成
<!--3 set-->
<sql id="updatesql">
<set>
<if test="title !=null">
title = #{title},
</if>
<if test="author != null">
author = #{author},
</if>
<if test="view != null">
view = #{view}
</if>
</set>
<where>
id=#{id}
</where>
</sql>
<update id="updateSet" parameterType="map">
update school.blog
<include refid="updatesql"/>
</update>
foreach循环
select * from school.blog where id in (1,2);的问题
public interface BlogMapper {
int addBlog(Blog blog);
List<Blog> queryBlogIf(Map map);
List<Blog> queryBlogChoose(Map map);
int updateSet(Map map);
List<Blog> selectTarget(Map map);
}
<select id="selectTarget" parameterType="map" resultType="Blog">
select * from school.blog
<where>
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id = #{id}
</foreach>
</where>
</select>
</mapper>
缓存
1、什么是缓存?
答:存在内存中临时数据,将用户经常访问的数据放在缓存中,而不用从磁盘上查询,从缓存中查询,从而提高查询效率,解决高并发系统的性能问题。
2、为什么使用缓存?
答:减少和数据库的交互次数,减少系统开销,提高系统效率
3、什么样的数据能使用缓存?
答:经常查询且不经常改变的数据。(反之,同理)
三高:高可用、高并发、高性能
①100个用户Tomcat:客户机→服务器→数据库
②1000个用户:客户机→N个服务器→数据库,读写问题(并发)?
③10000个用户:客户机→N个服务器→缓存服务器(读)→数据库(写)
④100000个用户:客户机→N个服务器→缓存服务器(读)→N个数据库(主、从),分布式(CAP)主从复制、读写分离。
Mybatis缓存
一级缓存(默认自动开启)
在应用运行过程中,我们有可能在一次数据库会话中,执行多次查询条件完全相同的SQL,MyBatis提供了一级缓存的方案优化这部分场景,如果是相同的SQL语句,会优先命中一级缓存,避免直接对数据库进行查询,提高性能。
每个SqlSession中持有了Executor,每个Executor中有一个LocalCache。当用户发起查询时,MyBatis根据当前执行的语句生成MappedStatement,在Local Cache进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入Local Cache,最后返回结果给用户。
@Test
public void getUserById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.getUserById(1);
System.out.println(user);
System.out.println("========================================================================");
User user2 = userMapper.getUserById(2);
System.out.println(user2);
//日志中只有一次执行sql记录。
}
修改了其他数据会导致缓存清空:
@Test
public void getUserById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.getUserById(1);
System.out.println(user);
//更新了其他2数据
Map map = new HashMap();
map.put("id",2);
map.put("pwd",290909);
userMapper.updateSet(map);
System.out.println("========================================================================");
//再次查询1数据的时候会清除缓存,从新从数据库查询
User user2 = userMapper.getUserById(1);
System.out.println(user2);
}
手动提交也会清理缓存
二级缓存(Cache类自定义,基于namespace的)
开启全局缓存
<!--mybatis-config中配置-->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<!--在当前Mapper中,先进先出策略,每隔60m刷新,最多存512个引用,返回的对象是只读的-->
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
<!--也可以在当前执行的方法中开启缓存-->
<select id="getUserById" resultType="User" useCache="true">
select * from school.user where id =#{id}
</select>
<!--关闭刷新缓存,数据库调优的时候会用-->
<update id="updateSet" parameterType="map" flushCache="false">
update school.user
<include refid="updatesql"/>
</update>
测试:
@Test
public void getUserTwoCache(){
SqlSession sqlSession1 = MybatisUtils.getSqlSession();
SqlSession sqlSession2 = MybatisUtils.getSqlSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user1 = userMapper1.getUserById(1);
System.out.println(user1);
sqlSession1.close();
//因为一个会话关闭了,会把当前数据缓存到二级缓存中,另外一个会话再次查询的时候就回去缓存中查找。
User user2 = userMapper2.getUserById(1);
System.out.println(user2);
sqlSession2.close();
结果
Mybatis进阶
Mybatis是通过建造器设计模式解析XML配置的每个子节点。
其中将Mybatis的全局配置文件((mapUnderscoreToCamelCase)配置、链接、事务、缓存)用XMLConfigBuilder读取并加载。每一个CRUD都解析成MapperStatement,放到configuration中。
其中Mapper的解析时是XMLMapperBuilder,会把每个Mapper构成一个HashMap<类,MapperProxyFactory>存储,通过getMap(),可以调用。解析resultMap提供返回值结果映射。Mapper的配置(二级缓存相关)的解析用到了装饰器设计模式。
而SQL的解析是XMLStatementBuilder,又采用组合设计模式遍历所有子节点,将不同节点解析成不同的Node,返回MixedSqlNode。最终每个select解析成MappedStatement(sqlSource,sql,resultMap,id)
执行SQL时,得到的SqlSession又采用了门面设计模式。SqlSession中封装了Executor执行器(配置默认的执行器),负责改(增删改)和查(SelectOne和SelectList)、(提交、关闭执行器、维护一级缓存)。最后就调用每个noded的apply方法组成SQL。
Mybatis的执行器有哪几种:SIMPLE、REUSE、BATCH,又通过父类BaseExecutor来实现一级缓存的查询,又通过装饰器设计模式的CacheExecutor类封装delete。创建执行器时,通过读取全局配置类中的settings来创建SqlSession,如果开启了二级缓存,又会CatcheExecutor包装BaseExecutor负责二级缓存。如果未开启,那么最外层的缓存是BaseExecutor。而后StatementHandler会执行参数解析(ParameterHandler),映射和结果映射。(ResultSetHandler)
// 数据源 执行器 DefaultSqlSession 2
SqlSession session = sqlSessionFactory.openSession();