Mybatis
1. 简介
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
MyBatis可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
1.1 持久化
数据持久化
持久化就是将程序的数据持久状态和瞬时状态转化过程
内存:断电即失
数据库(jdbc)io文件持久化
1.2 持久层
DAO层、Service层、Controller层
完成持久化工作的代码
层界十分明显
2、第一个Mybatis程序【重点】
思路:搭建环境-->导入Mybatis-->编写代码-->测试!
2.1、搭建环境
搭建数据库
CREATE DATABASE `mybatis`;
USE `mybatis`;
CREATE TABLE `user`(
`id` INT(20) NOT NULL PRIMARY KEY,
`name` VARCHAR(30) DEFAULT NULL,
`pwd` VARCHAR(30) DEFAULT NULL
)ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `user`(`id`,`name`,`pwd`) VALUES
(1,'狂神','123456'),
(2,'张三','123456'),
(3,'李四','123890')
新建项目
-
新建一个普通的maven项目
-
删除src目录
-
导入maven依赖
<!--导入依赖--> <dependencies> <!--mysql驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!--mybatis--> <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency> <!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies>
2.2、创建一个模块
-
编写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核心配置文件--> <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?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> </configuration>
-
编写mybatis工具类
//sqlSessionFactory --> sqlSession 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 (IOException e) { e.printStackTrace(); } } //既然有了 SqlSessionFactory,顾名思义,我们就可以从中获得 SqlSession 的实例了。 // SqlSession 完全包含了面向数据库执行 SQL 命令所需的所有方法。 public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); } }
2.3、编写代码
-
实体类
package com.kuang.pojo; //实体类 public class User { private int id; private String name; private String pwd; public User() { } public User(int id, String name, String pwd) { this.id = id; this.name = name; this.pwd = pwd; } 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 + '\'' + '}'; } }
-
Dao接口
public interface UserDao { List<User> getUserList(); }
-
接口实现类由原来的UserDaoImpl转变为一个 Mapper配置文件.
<?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接口--> <mapper namespace="com.kuang.dao.UserDao"> <!--select查询语句--> <select id="getUserList" resultType="com.kuang.pojo.User"> select * from mybatis.user </select> </mapper>
2.4、测试
注意点:
org.apache.ibatis.binding.BindingException: Type interface com.kuang.dao.UserDao is not known to the MapperRegistry.
MapperRegistry是什么?
核心配置文件中注册 mappers
-
junit测试
@Test public void test(){ //第一步:获得SqlSession对象 SqlSession sqlSession = MybatisUtils.getSqlSession(); //方式一:getMapper UserDao userDao = sqlSession.getMapper(UserDao.class); List<User> userList = userDao.getUserList(); for (User user : userList) { System.out.println(user); } //关闭SqlSession sqlSession.close(); }
你们可以能会遇到的问题:
- 配置文件没有注册
- 绑定接口错误。
- 方法名不对
- 返回类型不对
- Maven导出资源问题
3. CRUD
1. namespace
<!--nameSpace要与接口的类名一致-->
namespace="cn.gdcp.mapper.StudentMapper"
2.select
<select id="getStudentList" resultType="cn.gdcp.pojo.Student02">
select * from dm_student
</select>
3. insert
<select id="addStudent" parameterType="cn.gdcp.pojo.Student02" resultType="java.lang.Integer">
insert into dm_student (id,name,major,sno) values(#{id},#{name},#{major},#{sno})
</select>
4. update
<update id="updateStudent" parameterType="cn.gdcp.pojo.Student02">
update dm_student set name=#{name},major=#{major},sno=#{sno} where id=#{id} ;
</update>
5. delete
<delete id="deleteStudent" parameterType="int" >
delete from dm_student where id=#{id}
</delete>
注意点:
增删改需要提交事务
6.分析错误
- 标签匹配错误
- mapper标签的resource是路径以斜杠划分
7. 万能map
当我们实体类,或者数据库的表。字段或者参数过多,考虑用Map!
//万能map的insert
int addStudent02(Map<String,Object> map);
<insert id="addStudent02" parameterType="map" >
insert into dm_student (id,name,major,sno) values(#{s_id},#{s_name},#{s_major},#{s_sno})
</insert>
//万能map
@Test
public void updateStudent02(){
SqlSession session = MyBatisUtils.getSession();
StudentMapper mapper = session.getMapper(StudentMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("s_id",4);
map.put("s_name","mgl");
map.put("s_major","boss");
map.put("s_sno","110");
int updateStudent02 = mapper.updateStudent02(map);
if (updateStudent02 > 0) {
System.out.println("修改成功!");
}
session.commit();
session.close();
}
Map传递参数,直接在sql中取出key即可 [parameterType="map"]
对象传递参数,直接在sql中取出对象的属性即可[parameterType="Object"]
多个参数用Map,或者注解
8. 模糊查询
1.java代码,传递通配符% %
List<Student02> student02Like = mapper.getStudentLike02("%李%");
2.sql拼接中使用通配符% %
select * from dm_student where name like "%"#{name}"%"
3. 配置解析
1. 核心配置文件
mybatis-config.xml
configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- environment(环境变量)
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)
2. 属性(properties)
核心配置文件有同一字段,优先使用外部配置文件的
3. 类型别名(typeAliases)
类型别名可为 Java 类型设置一个缩写名字。
<!-- 第一种配置 -->
<!--<typeAlias type="cn.gdcp.pojo.User" alias="user"></typeAlias>-->
<!-- 第二种配置 默认实体的类名就是别名,大小写不区分 首字母小写 -->
<package name="cn.gdcp.pojo"/>
如果非要改,与需要在实体上增加注释
Alias("student02")
public class Student02{}
4. 映射器mappers
-
<!-- mapper.xml文件路径配置 --> <mappers> <mapper resource="cn/gdcp/mapper/StudentMapper.xml"></mapper> </mappers>
-
class文件绑定注册
注意点:
接口与它的Mapper配置文件必须同名
接口与它的Mapper配置文件必须在同一包下
-
使用扫描包注册绑定
注意点:
接口与它的Mapper配置文件必须同名
接口与它的Mapper配置文件必须在同一包下
5. 生命周期和作用域
生命周期、和作用域、至关重要的,因为错误的使用导致非常严重的并发问题
SqlSessionFactoryBuilder:
一旦创建了 SqlSessionFactory,就不再需要它了
局部变量
SqlSessionFactory:
想象成:数据库连接池
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
因此 SqlSessionFactory 的最佳作用域是应用作用域
最简单的就是使用单例模式或者静态单例模式。
SqlSession:
连接到连接池的一个请求
SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
用完之后赶紧关闭,否则会占用资源
每一个Mapper,代表一个业务
5. 解决属性名和字段名不一致的问题
1.起别名问题
select id,name as NAME,major,sno from dm_student where id=#{id}
2.resultMapper
<select id="getStudentId" resultMap="StudentMap">
select * from dm_student where id=#{id}
</select>
<!-- 结果集映射-->
<resultMap id="StudentMap" type="Student02">
<!-- column数据库的字段,property实体类的变量属性-->
<id column="id" property="id"></id>
<result column="name" property="NAME"></result>
<result column="major" property="major"></result>
<result column="sno" property="sno"></result>
</resultMap>
resultMap
元素是 MyBatis 中最重要最强大的元素
ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
6. 日志
1. 日志工厂
LOG4J/STDOUT_LOGGING
STDOUT_LOGGING标准日志输出
在核心配置文件,配置我们的日志
2.LOG4J
使用
-
在使用log4j的类中,导入包
-
日志对象,参数为当前类的class
static Logger logger = Logger.getLogger(StudentTest.class);
-
日志级别
public void log4jTest(){ logger.info("info:进入了log4jTest"); logger.debug("info:进入了log4jTest"); logger.error("info:进入了log4jTest"); }
7. limit分页
limit分页
select * from Student limit satrtIndex,pageSize
mybatis
1.接口
//分页查询
List<Student02> getStudent02Limit(Map<String,Object> map);
2.Mapper.xml
<!-- 分页查询-->
<select id="getStudent02Limit" parameterType="map" resultType="Student02">
select * from dm_student limit #{startIndex},#{pageSize}
</select>
8. 使用注解开发
1.注解在接口上实现
@select(select * form dm_student)
List<Student02> getStudents()
2.在核心配置文件绑定接口
<!-- 绑定接口 -->
<!-- <mapper class="cn.gdcp.mapper.StudentMapper"/>-->
3.CRUD
我们可以再工具类创建的时候实现自动提交事务!
public static SqlSession getSession() {
return sqlSessionFactory.openSession(true);
}
编写接口,添加注解
//id查询
//方法存在多个参数,所有参数前同必须加上@Param("id")注解
@Select("select * from dm_student where id=#{id}")
Student02 getStudentId(@Param("id") int id);
//分页查询
@Select("select * from dm_student limit #{startIndex},#{pageSize}")
List<Student02> getStudent02Limit(Map<String,Object> map);
//insert
@Insert("insert into dm_student (id,name,major,sno) values(id=#{id},name=#{name},major=#{major},sno=#{sno})")
int addStudent(Student02 student02);
//update
@Update("update dm_student set name=#{name},major=#{major},sno=#{sno} where id=#{id}")
int updateStudent02(Student02 student02);
//delete
@Delete("delete from dm_student where id=#{id}")
int deleteStudent02(int id);
关于@Param()注解
基本类型的参数或者String类型,需加上
引用类型不需要加
如果只有一个基本类型,可以省去,但建议大家加上
SQL中引用的就是这里的@Param中设定的属性名
9.Lombok
常用的注解
@Data:无参构造、get、set、toString、hashCode、equals
@AllArgsConstructor
10.多对一处理【重点】
- 多个学生,对应一个老师
- 对于学生而言,关联多个学生,关联一个老师【多对一】
按照查询嵌套处理
<!--按照查询嵌套处理-->
<!--思路:1.查询所有的学生信息2.根据将查询出来的学生记录信息,需找对应的老师-->
<select id="getStudent" resultMap="StudentTeacher">
select * from student
</select>
<resultMap id="StudentTeacher" type="Student">
<id column="id" property="id"></id>
<result column="name" property="name"></result>
<!--复杂的属性,需要单独处理,对象关联多对一:association,一对多集合:collection-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"></association>
</resultMap>
<select id="getTeacher" resultType="Teacher">
select * from teacher where id=#{id}
</select>
按照结果嵌套查询
<!-- 按照结果嵌套查询-->
<!-- 按照结果嵌套查询-->
<select id="getStudent02" resultMap="StudentTeacher02">
select s.id sid,s.name sname,t.name tname from student s,teacher t where s.tid=t.id
</select>
<resultMap id="StudentTeacher02" type="Student">
<id column="sid" property="id"></id>
<result column="sname"property="name"></result>
<association property="teacher" javaType="Teacher">
<result column="tname" property="name"></result>
</association>
</resultMap>
注意点:多对一
- 子查询
- 联表查询
11.一对多【重点】
对于老师而言,集合,一个老师,有很多学生【一对多】
按照查询嵌套处理
<!-- 按照查询嵌套查询-->
<select id="getStudent03" resultMap="TeacherStudent02">
select * from teacher where id=#{tid}
</select>
<resultMap id="TeacherStudent02" type="Teacher">
<id column="id" property="id"></id>
<result column="name" property="name"></result>
<collection property="student" javaType="ArrayList" ofType="Student" select="getStudentTeacherId" column="id"></collection>
</resultMap>
<select id="getStudentTeacherId" resultType="Student">
select * from student where tid=#{tid}
</select>
按照结果嵌套查询
<!-- 按照结果嵌套查询-->
<select id="getTeacher02" resultMap="TeacherStudent">
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="TeacherStudent" type="Teacher">
<id column="tid" property="id"></id>
<result column="tname" property="name"></result>
<!--复杂的属性,需要单独处理,对象关联多对一:association,一对多集合:collection-->
<!--javaType指定属性的类型,集合中的泛型信息List,使用ofType获取-->
<collection property="student" ofType="Student" >
<id column="sid" property="id"></id>
<result column="sname" property="name"></result>
<result column="tid" property="tid"></result>
</collection>
</resultMap>
小结
1.关联:association[多对一]
2.集合:collection[一对多]
3.javaType & ofType
-
javaType用来指定实体类中的属性的类型
-
ofType:用来指定映射到list或者集合中的pojo类型,泛型中的约束类型!
3.面试高频
- Mysql引擎
- innoDB底层原理
- 索引
- 索引优化
12. 动态SQL
含义:动态sql就是指根据不同的条件生成不同的SQL语句
if
choose (when,otherwise)
trim(where,set)
foreach
IF
<select id="queryBlogIF" resultType="Blog" parameterType="map">
select * from blog where 1=1
<if test="title!=null">
title=#{title}
</if>
<if test="author!=null">
and author=#{author}
</if>
</select>
choose(when,otherwise)
<select id="queryBlogChoose" resultType="Blog" parameterType="map">
select * from blog
<where>
<choose>
<when test="title!=null">
title=#{title}
</when>
<when test="author!=null">
and author=#{author}
</when>
<otherwise>
and views=#{views}
</otherwise>
</choose>
</where>
</select>
trim(where,set)
<select id="queryBlogIF" resultType="Blog" parameterType="map">
select * from blog
<where>
<if test="title!=null">
title=#{title}
</if>
<if test="author!=null">
and author=#{author}
</if>
</where>
</select>
<update id="updateBlog" parameterType="map">
update blog
<set>
<if test="title!=null">
title=#{title},
</if>
<if test="author!=null">
and author=#{author}
</if>
</set>
where id=#{id}
</update>
SQL片段
1.使用SQL标签抽取公共部分
<sql id="if-title-author">
<if test="title!=null">
title=#{title}
</if>
<if test="author!=null">
and author=#{author}
</if>
</sql>
2.需要使用的地方使用include标签引用即可
<select id="queryBlogIF" resultType="Blog" parameterType="map">
select * from blog
<where>
<include refid="if-title-author"></include>
</where>
</select>
注意:最好基于单表使用SQL片段
Foreach
<select id="queryBlogForeach" parameterType="map" resultType="Blog">
select * from blog
<where>
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id=#{id}
</foreach>
</where>
</select>
动态SQL就是在拼接SQL语句
13. 缓存
1. 简介
2. Mybatis缓存
MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。
3.一级缓存
默认情况下,只启用了本地的会话缓存,它仅仅对一次会话中的数据进行缓存。
缓存失效的情况:
-
不同的查询
-
增删改操作,会改变原来的数据,必定会刷新缓存
Opening JDBC Connection
Created connection 1850180796.
==> Preparing: select * from users where uid=?
==> Parameters: 1(Integer)
<== Columns: uid, user_name, uage
<== Row: 1, 张三, 20
<== Total: 1
User(id=0, userName=张三, age=0)
==> Preparing: update users set uage=?,user_name=? where uid=?
==> Parameters: 12(Integer), mgl(String), 2(Integer)
<== Updates: 1
1
==> Preparing: select * from users where uid=?
==> Parameters: 1(Integer)
<== Columns: uid, user_name, uage
<== Row: 1, 张三, 20
<== Total: 1
User(id=0, userName=张三, age=0)
false
-
查询不同的mapper.xml
-
手动清理缓存
SqlSession session = MyBatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User userById = mapper.getUserById(1);
System.out.println(userById);
// int mgl = mapper.updateUser(new User(2, "mgl", 12));
// System.out.println(mgl);
session.clearCache();//手动清理缓存
User userById2 = mapper.getUserById(1);
System.out.println(userById2);
System.out.println(userById==userById2);
session.close();
4. 二级缓存
- 开启全局缓存
<!-- 开启全局缓存,默认true -->
<setting name="cacheEnabled" value="true"/>
2.只需要在你的Mapper 映射文件中添加一行:
<!-- 使用Mapper.xml开启二级缓存-->
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
小结:
- 只要开启了二级缓存,在同一个mapper下就有效
- 所有的数据都会先放在一级缓存下
- 只有会话提交,或者关闭时,才会提交到二级缓存中
5. 缓存的原理
缓存的顺序:先查二级缓存,再查一级缓存,最后查数据库
6. 自定义缓存EhCache
EhCache 是一个纯Java的进程内缓存框架,具有快速、精干。