什么是 MyBatis?
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
快速入门程序
- 环境配置:使用的maven搭建项目,导入依赖即可
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
- 搭建dao层逻辑以及数据库操作(MySQL)
实体类
package cn.supperbro.domain;
public class School {
private int id;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "School{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
接口:
package cn.supperbro.dao;
import cn.supperbro.domain.School;
import java.util.List;
public interface UserMapper {
List<School> getSchool();
}
sql映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace指向持久层的接口-->
<mapper namespace="cn.supperbro.dao.UserMapper">
<!--id表示的是接口中的方法 resultType表示的返回的结果集类型-->
<select id="getSchool" resultType="school">
select * from school
</select>
</mapper>
- 配置XML文件(方式很多,可以通过纯java配置)
采用的是xml配置mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--引入数据库配置文件-->
<properties resource="db.properties">
<!--使用配置文件的同时,还可以通过name来进行赋值-->
<property name="username" value="root"/>
</properties>
<!--配置操作-->
<settings>
<!--此处配置了日志操作-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<typeAliases>
<!--别名操作-->
<typeAlias type="cn.supperbro.domain.School" alias="school"/>
</typeAliases>
<!--选择执行的环境-->
<environments default="development">
<!--执行的环境,可以自定义多个-->
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${URL}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!--探究SQL映射-->
<mappers>
<mapper resource="cn/supperbro/dao/UserMapper.xml"/>
</mappers>
</configuration>
当然这里是通过绑定数据库配置文件db.properties进行配置
driver=com.mysql.cj.jdbc.Driver
#mysql8.0以上需要添加时区serverTimezone=UTC或者Asia/Shanghai...
URL=jdbc:mysql://localhost:3306/jdbctest?serverTimezone=Asia/Shanghai&useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=
password=
- 获取SqlSession对象
通过MybatisUtil.java工具类来获取
package cn.supperbro;
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;
/**
* @author 18175
* Mybatis获取对象的工具类
*/
public class MybatisUtil {
private static SqlSessionFactory sqlSessionFactory;
/**
* SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。
*/
static
{
try {
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();
}
}
- 测试
采用junit测试注意导入依赖
@org.junit.Test
public void test01(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<School> school = mapper.getSchool();
for (School school1 : school) {
System.out.println(school1);
}
/*
School{id=1, name='武汉商学院'}
School{id=2, name='武汉大学'}
School{id=3, name='武汉理工大学'}
*/
}
注意:
- 如果是Mapper接口与Mapper.xml在同一个路径下,同样在java资源包下,由于maven约定大于配置,需要在pom.xml中配置资源过滤的操作
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>
true
</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>
true
</filtering>
</resource>
</resources>
</build>
- 关于SqlSession SqlSessFactory SqlSessionFactoryBuilder的作用域
SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
SqlSessionFactory
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。
关于mybatis配置
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
settings(设置)常用的配置
logImpl
指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
类型别名(typeAliases)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<typeAlias alias="Comment" type="domain.blog.Comment"/>
<typeAlias alias="Post" type="domain.blog.Post"/>
<typeAlias alias="Section" type="domain.blog.Section"/>
<typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>
也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。
<typeAliases>
<package name="domain.blog"/>
</typeAliases>
映射器(Mapper)
绑定mapper文件
-
通过resource去指定
<mappers> <mapper resource="org/mybatis/builder/AuthorMapper.xml"/> <mapper resource="org/mybatis/builder/BlogMapper.xml"/> <mapper resource="org/mybatis/builder/PostMapper.xml"/> </mappers>
-
通过class指定 :接口与xml命名必须一致 必须在同一个包下
<!-- 使用映射器接口实现类的完全限定类名 --> <mappers> <mapper class="org.mybatis.builder.AuthorMapper"/> <mapper class="org.mybatis.builder.BlogMapper"/> <mapper class="org.mybatis.builder.PostMapper"/> </mappers>
-
通过包名去指定
<!-- 将包内的映射器接口实现全部注册为映射器 --> <mappers> <package name="org.mybatis.builder"/> </mappers>
解决字段名与bean的属性名不一致的问题ResultMapper
-
可以通过给sql语句起别名让其一一对应 (不推荐)
-
ResultMap
resultMap
元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBCResultSets
数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份resultMap
能够代替实现同等功能的数千行代码。ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。<!--结果集映射--> <resultMap id="UserMap" type="user"> <!--column表示database中的字段 property表示Java的属性值--> <result column="username" property="name" /> </resultMap> <!--id表示的是接口中的方法 resultType表示的返回的结果集类型 resultMap对应的结果集映射--> <select id="findUser" resultType="user" resultMap="UserMap"> select * from students; </select>
案例——处理复杂查询
多对一的处理
1.按照查询嵌套查询
实现两个表的查询的联合查询
<select id="findST" resultMap="ST">
select *
from students;
</select>
<select id="findT" resultType="teacher">
<!--此处id的取值参数的value可以随意填写,mybatis有对应其关系的机制-->
select * from teachers where id = #{t_id}
</select>
<resultMap id="ST" type="student">
<!--表示在javaBean中有一个属性为对象 db中对应的参数 对象名 对象类型 绑定查询语句-->
<association property="teacher" column="t_id" javaType="teacher" select="findT"/>
</resultMap>
2.按照结果嵌套查询
<resultMap id="ST2" type="student">
<result column="sname" property="username"/>
<result property="score" column="score"/>
<association property="teacher" javaType="teacher">
<result column="tname" property="username"/>
</association>
</resultMap>
<select id="findST2" resultMap="ST2">
select s.username as sname, s.score as score ,t.username as tname
from students s,
teachers t
where s.t_id = t.id;
</select>
一对多时
也可以通过查询嵌套或者是结果嵌套的方式查询
<resultMap id="ST" type="teacher">
<result property="username" column="tname"/>
<!--collection一对多查询时 在属性中有集合属性 可以选用collection-->
<collection property="students" ofType="student">
<result property="username" column="sname"/>
<result property="score" column="score"/>
<result property="t_id" column="tid"/>
</collection>
</resultMap>
<select id="findST" resultMap="ST">
select s.username as sname ,s.score as score , s.t_id as tid , t.username as tname
from students s,
teachers t
where t.id = s.t_id;
</select>
<select id="findST2" resultMap="st2">
select * from teachers
</select>
<select id="st" resultType="student">
select * from students where t_id = #{id}
</select>
<resultMap id="st2" type="teacher">
<collection property="students" ofType="student" column="id" select="st"/>
</resultMap>
一对多 association
多对一 collection
javaType 指定属性的类型
ofType 映射集合中的的pojo的泛型…指定集合中泛型的类型
动态sql
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。
如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
if
mapper.xml
<select id="findStIf" resultType="student" parameterType="map">
select *
from students
<if test="id!=null">
where id = #{id}
</if>
<if test="username!=null">
or username = #{username}
</if>
</select>
测试
@org.junit.Test
public void test02(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Map<String,Object> map = new HashMap<String,Object>();
map.put("username","zyx");
map.put("id",2);
List<Student> students = mapper.findStIf(map);
for (Student student : students) {
System.out.println(student);
}
sqlSession.close();
}
增加where元素
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
<select id="findStIf" resultType="student" parameterType="map">
select *
from students
<where>
<if test="id!=null">
id = #{id}
</if>
<if test="username!=null">
or username = #{username}
</if>
</where>
</select>
choose (when, otherwise)
我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
<select id="findStChoose" parameterType="map" resultType="student">
select *
from students
<where>
<choose>
<when test="username!=null">
username = #{username}
</when>
<when test="id!=null">
and id = #{id}
</when>
<otherwise>
and id = 18
</otherwise>
</choose>
</where>
</select>
@org.junit.Test
public void test03(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Map<String,Object> map = new HashMap<String,Object>();
//map.put("username","zyx");
// map.put("id",2);
List<Student> students = mapper.findStChoose(map);
for (Student student : students) {
System.out.println(student);
}
sqlSession.close();
}
trim (where, set)
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
**如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。**比如,和 where 元素等价的自定义 trim 元素为:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
**prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。**上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。
set
用于动态更新语句的类似解决方案叫做 set。set 元素可以用于**动态包含需要更新的列,**忽略其它不更新的列。set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号
<select id="findStSet2" parameterType="map" resultType="student">
update students
<set>
<if test="username!=null">
username = #{username},
</if>
<if test="score!=null">
score=#{score},
</if>
</set>
where id = #{id}
</select>
@org.junit.Test
public void test05(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Map<String,Object> map = new HashMap<String,Object>();
map.put("username","吴亦凡");
map.put("score",99.66);
map.put("id",2);
List<Student> students = mapper.findStSet2(map);
for (Student student : students) {
System.out.println(student);
}
sqlSession.close();
}
sql片段
<sql id="if">
<if test="id!=null">
id = #{id}
</if>
<if test="username!=null">
or username = #{username}
</if>
</sql>
<select id="findStIf" resultType="student" parameterType="map">
select *
from students
<where>
<include refid="if"></include>
</where>
</select>
把相同的sql片段给提取出,增强他的复用性
Foreach
foreach 元素的功能非常强大,它允许你指定一个**集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。**这个元素也不会错误地添加多余的分隔符,看它多智能!
可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
<select id="findStForeach" resultType="student" parameterType="list">
select * from students where id in
<foreach collection="list" index="index" item="item" open="(" separator="," close=")" >
#{item}
</foreach>
</select>
使用注解开发
package org.mybatis.example;
public interface BlogMapper {
@Select("SELECT * FROM blog WHERE id = #{id}")
Blog selectBlog(int id);
}
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
选择何种方式来配置映射,以及认为是否应该要统一映射语句定义的形式,完全取决于你和你的团队。 换句话说,永远不要拘泥于一种方式,你可以很轻松的在基于注解和 XML 的语句映射方式间自由移植和切换。
//参数传递
@Select("select id,username as name,password,score from students where username = #{username} and id = #{id}")
List<User> getUserByUsername(@Param("username")String username,@Param("id")Integer id);
//@Param可以设置参数的id #{}引用时必须填该id
了解Lombok
Lombok能以简单的注解形式来简化java代码,提高开发人员的开发效率。例如开发中经常需要写的javabean,都需要花时间去添加相应的getter/setter,也许还要去写构造器、equals等方法,而且需要维护,当属性多时会出现大量的getter/setter方法,这些显得很冗长也没有太多技术含量,一旦修改属性,就容易出现忘记修改对应方法的失误。
Lombok能通过注解的方式,在编译时自动为属性生成构造器、getter/setter、equals、hashcode、toString方法。出现的神奇就是在源码中没有getter和setter方法,但是在编译生成的字节码文件中有getter和setter方法。这样就省去了手动重建这些代码的麻烦,使代码看起来更简洁些。
使用步骤
idea安装Lombok
在项目中导入Lombok的包
@Data注解在类上,会为类的所有属性自动生成setter/getter、equals、canEqual、hashCode、toString方法,如为final属性,则不会为该属性生成setter方法。
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
缓存
查询:连接数据库比较耗资源
一次查询的结果缓存在一个地方—>内存
当再次查询到相同的数据时可以去缓存中寻找
MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。
默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:
<cache/>
一级缓存
只在sqlSession中有效,也就是拿到链接和关闭连接的一段时间
sqlSession.clearCache();
清除缓存
二级缓存
基于namespace级别的缓存
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
步骤:
配置核心配置文件setting标签
//默认也是开启的
<setting name="cacheEnabled" value="true"/>
//zainamespace设置缓存
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
注意事项
使用默认的
需要将实体类序列化
public class Student implements Serializable {......}
- 只要开启了二级缓存,在同一个mapper就会有效
- 所有的数据都会放在一级缓存中
- 只有当会话提交,或者是关闭的时候,才会提交到二级缓存中
基本了解就到这了,官网才是学习的好地方