Mybatis学习笔记
Mybatis是什么?
MyBatis 是一款优秀的持久层框架,支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
一、Mybatis配置
1.环境搭建
Mybatis框架可以通过Maven来使用,Maven配置在之前的Maven学习周报中已经阐明了步骤,无外乎就是下载包,配置文件,CMD运行安装三步走。
要使用Maven来调用Mybatis框架,首先需要创建一个Maven项目,创建好以后,在Mybatis对应的子项目的pom.xml文档中,修改依赖项,添加以下代码:
<dependency>
<groupid>org.mybatis</groupid>
<artifactid>mybatis</artifactid>
<version>x.x.x</version><!--这里我用的版本是3.5.2-->
</dependency>
接着刷新Maven等待下载对应jar包就可以了。
Mybatis是一个持久层的框架,用于简化本来的JDBC相关代码,由于要与数据库打交道,所以还是有一些通用的东西需要,先考虑Maven项目之间的依赖。Mybatis需要有对应数据库的驱动jar包,我用的是SqlServer,所以在Maven仓库里找到必要依赖以及对应添加代码如下:
<!-- https://mvnrepository.com/artifact/com.microsoft.sqlserver/mssql-jdbc -->
<dependency>
<groupid>com.microsoft.sqlserver</groupid>
<artifactid>mssql-jdbc</artifactid>
<version>9.2.1.jre8</version>
</dependency>
为了方便测试,我这里还添加了junit的依赖,不过这并不是必需
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupid>junit</groupid>
<artifactid>junit</artifactid>
<version>4.12</version>
<scope>test</scope>
</dependency>
再来就是运用之前学的SQL语句创建一个数据库
CREATE DATABASE Mybatis;
USE Mybatis;
CREATE TABLE USERS(
ID INT NOT NULL PRIMARY KEY,
NAME VARCHAR(25) NOT NULL,
PWD VARCHAR(25) NOT NULL
)
INSERT USERS(ID,NAME,PWD)VALUES
(00001,'XiaoMing','123456'),
(00002,'ZhangSan','54321'),
(00003,'Li','67890')
这样基本环境就解决了
2.核心配置
在Mybatis的官方文档中有这么一句话,很好的阐述了Mybatis的核心。
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
即Mybatis的运转,都是以SqlSessionFactory实例为核心,这个核心我们需要配置好XML配置文件后用SqlSessionFactoryBuilder方法获得。
但是这里的最关键的问题还是XML的配置,这里有一个最基本的配置如下:
<!--?xml version="1.0" encoding="UTF-8" ?-->
<!--以下部分为Mybatis最基础的的核心配置-->
<configuration>
<environments default="development"><!--如果你调用了带 environment 参数的 build 方法,那么将使用该环境对应的配置,此处为默认-->
<environment id="development">
<transactionmanager type="JDBC">
<datasource type="POOLED">
<property name="driver" value="${driver}"><!--以下为JDBC连接数据库所必须的内容包括驱动,URL,账户密码信息-->
<property name="url" value="${url}">
<property name="username" value="${username}">
<property name="password" value="${password}">
</property></property></property></property></datasource>
</transactionmanager></environment>
</environments>
<!--下面的部分是Mapper的注册-->
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml">
</mapper></mappers>
</configuration>
可以看到,这个最基本的配置信息涵盖了之前JDBC连接数据库所必需所有信息,有了这个文档,我们就可以通过调用SqlSessionFactoryBuilder方法来获取SqlSessionFactory实例了。有了它,我们就可以通过调用方法来获取SqlSession的实例了。而SqlSession提供了在数据库中执行sql语句的大部分方法,本周我所学习的是利用一个xml配置文档来对接口进行映射从而实现sql语句的方法,下面我就来详谈这个方法。
二、Mybatis首次实践
首先,要实现Mybatis运行sql,我们还是需要以前JDBC时所使用的工具类,包括数据对象类,Dao类或者说Mapper类,Utils工具类。但是因为使用了Mybatis,所以还是会有些许不同。
首先实现数据对象类,这个类主要是提供操作数据库中一个元组对象的方法的,要与数据库对应,前面我已经创建好了数据库,有ID,Name,PWD三个数据项,那么这里数据对象类也要如此。如下:
package 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;
}
//getter 和 setter
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;
}
//重载的toString()方法
@Override
public String toString() {
return "User{" +
"ID=" + ID +
", NAME='" + NAME + '\'' +
", PWD='" + PWD + '\'' +
'}';
}
}
有了数据对象类,我们还需要一个util工具类,util工具在有了Mybatis以后,目的就是完成SqlSessionFactory实例的创建并返回我们需要的SqlSession实例,以便我们引用sql映射。那么就可以按照官网给出的经典三段式来写util类:
//以下是官网给出的经典三段式创建SqlSession
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
可以看出我们要通过之前核心配置文件,调用Mybatis提供的Resource类的方法来读取文件,最后利用读取的输入流创建实例,最后还要返回SqlSession对象。具体代码如下:
package 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.*;
//sqlSessionFactory实例创建工具类
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;//提前将我们需要的类声明为null,这样就可以将创建方法设为初始化时自动执行
static {
InputStream inputStream = null;
try {
String resource = "mybatis-config.xml"
inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//三段式创建
} catch (IOException e) {
e.printStackTrace();
}
}
//返回SqlSession对象用单独的方法,方便调用
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
Tips:这里sqlSessionFactory对象的openSession()方法是可以有参数的,这个参数为一个布尔值,该值决定了我们创建的连接SqlSession是否会自动地提交事务,如果这里写上一个true,那么在接下来的实践中,我们就无需手动地通过commit()方法来提交事务了
接下来就是接口类以及映射文件了,首先写映射文件,这里有一个官网给出的例子:
<!--?xml version="1.0" encoding="UTF-8" ?-->
<mapper namespace="org.mybatis.example.BlogMapper">
<select id="selectBlog" resulttype="Blog">
select * from Blog where id = #{id}
</select>
</mapper>
可以看出,除了头部的通用样式外,映射以·</mapper>
结尾,其中的内容视我们所需要进行的sql语句操作而定。而namespace则需要写入映射接口类名,id则是方法名,resultType表示此方法需要返回的数据类。那么就很明确了,namespace和id与我们即将要写的接口类要对应,而返回数据类则应该与我们之前写的数据类对应,所以我们的程序应该如下配置:
<!--?xml version="1.0" encoding="UTF-8" ?-->
<mapper namespace="Dao.UserDao"><!--这是之后要写的接口类名-->
<select id="getUserList" resulttype="pojo.User"><!--getUserList为方法,type则是我之前写的数据类名-->
select * from Users<!--执行的sql语句-->
</select>
</mapper>
只需要完成查询,接口可以写的非常简单:
package Dao;
import pojo.User;
import java.util.List;
public interface UserDao {
List<user> getUserList();//调用xml文件中提供的映射,返回List<user>,返回类为pojo.User
}
这里有一点必须注意,写好的映射必须要在核心配置文件下的<mapper>...</mapper>
中进行注册,否则会无法读入配置xml文件
如此一来我们的工作就完成了,接下来写一个测试程序来看看效果如何:
package Dao;
import org.apache.ibatis.session.SqlSession;
import utils.MybatisUtils;
import pojo.*;
import Dao.*;
import java.util.List;
import org.junit.Test;
public class UserDaotest {
@Test//Test注解,用于运行test程序
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();//通过util类返回我们需要的SqlSession对象
UserDao userdao = sqlSession.getMapper(UserDao.class);//调用接口类,返回接口对象
List<user> userList = userdao.getUserList();//使用接口方法返回sql查询结果
for(User user:userList){
System.out.println(user);//输出查询结果
}
sqlSession.close();//要记得,所有的Session都必须要及时关闭,不要让它们持续占用资源
}
}
最终结果如下:
![2021-08-15 (1)](C:\Users\24779\OneDrive\图片\屏幕快照\2021-08-15 (1).png)
实验成功。
总结一下,Mybatis使用就是三步,首先要编写核心配置文档,再就是编写工具类,最后编写映射文档,三步以后就可以利用Mybatis来实现数据库的操作了。
不过这里还有一个小问题必须要说一下,首先是Maven经常会有的一个问题,就是src文件夹下的xml文件经常无法读入Target文档,造成无法找到资源的报错,这里需要更改一下pom.xml文档,内容如下:
<!--在build中配置resoureces,来防止资源导出失效的问题-->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
主要就是为了防止无法读出src文件夹下的.xml文档和.properties文档。
三、映射器mapper文件编写
1、Mapper.xml文件标签意义
- 增删改查内部标签
- namespace:namespace中的包名 一定要和对应Mapper.java的全路径名保持一致
- id:对应namespace中的方法名,代表的是接下来要写的sql语句的方法名
- resultType:指的是sql语句的返回值的类型
- resultMap:同样是sql语句返回值的类型,不过resultMap可以自定义一些内容
- parameterType:参数类型,如果是基础数据类型则只需要填入类型名,如果是类则需要写明实体类的全名
2、Mapper.xml增删改查
下面是增删改查的示例,对于Mybatis,只要设置好了第一次,后续再进行更改时,只需要修改Mapper,Mapper.xml,以及对应的运行代码即可,这里只讲最核心的Mapper.xml的内容。
1、insert
<insert id="addUser" parametertype="pojo.User">
<!--这里addUser是借口类中的对应方法名,parameterType是参数类型,参数类型为User实体类全名-->
insert Users (ID,NAME,PWD) values (#{ID},#{NAME},#{PWD})
<!--这是真正需要执行的sql语句,其中#{}表示引用,主要用于引用输入的数据,所以要指明引用输入数据的什么-->
</insert>
2、delete
<delete id="deleteUser" parametertype="int">
<!--此处同上,需要注意的是增删改是没有返回值类型这一设置的-->
delete from Users where id = #{id}
</delete>
3、update
<update id="updateUserById" parametertype="pojo.User">
update Users set name = #{NAME}, pwd = #{PWD} where id = #{ID}
</update>
4、select
<select id="getUserById" resulttype="pojo.User" parametertype="int">
<!--注意这里有区别了,查询操作有返回值,所以需要设置返回值类型-->
select * from Users where id = #{id}
</select>
Tips:这里有个非常重要的问题,增删改的操作一定要在具体的运行代码那里用sqlsession类的commit()方法来提交事务,否则是无法成功的,不要忘了事务!!!
Tips:补充一下#{}
与${}
都可以表示引用,但是#{}
使用的是预编译了的sql语句,可以很大程度上的防止sql注入的问题,而${}
则使用的是普通的sql语句,故推荐全部使用#{}
来进行引用
3、SQL片段保存与复用
在mapper.xml文件中,你可能会希望保存某一段通用的SQL语句以便以后再度使用它,那么Mybatis中也确实有这种方法,具体操作需要引入一个标签:
sql标签,在Mapper.xml中可以如下创建一个sql片段以便任何时候调用。
<sql id="sql_1">
<if test="title !=null">
and title = #{title}
</if>
<if test="author !=null">
and author = #{author}
</if>
</sql>
很简单的方法,就是用sql标签把需要复用的sql语句完全框起来就可以了,id即为引用这段sql的记号,有了id我们就可以这样引用上面这段sql:
<select id="getBlog" resulttype="Blog" parametertype="map">
select * from Blogs
</select>
也非常简单,就是在include标签下,通过refid属性输入需要引用的sql的id,即可引用成功。
4、ResultMap结果集映射
在这里有个新的问题需要解决,之前一直没有提到,从数据库中获取了查询数据后,数据来到Java程序这边时,需要一个实体类作为这些数据的载体,这个实体类在上面就体现为pojo.User
这个类,但是!有个很重要的问题就是Mybatis怎么知道获取的表中数据和实体类元素的对应关系的呢?这一问题在上面的例子中并没有得到体现,这是因为在数据库的表中的列名与实体类名在之前的设置中我们已经将其一一完全对应,即
DataBase: id name pwd
Class: id name pwd
正是因为一一对应,所以Mybatis在处理数据时进行了简略处理,把获得的数据放到实体类的对应名字的属性上就好。
但是有时候我们不会取,或是有可能不会取一一对应的名字,这样就会导致查询的不稳定,如果我们把上面的关系换成这样:
DataBase: id name pwd
Class: id name password
无论这么做的原因为何(因为显然这是自找麻烦),这样做的结果是非常简单的,那就是在查询返回结果时,会输出为
{ id:id, name:name, password: null}
也就是password的返回值为null,即没有取得返回值(String类默认值为null),原因就是映射关系的不明确。这就要在Mapper.xml中引入ResultMap这一设置了。
ResultMap是与前面resultType相对应的标签,即表明返回值为resultMap所设定的实体类映射关系,这一配置需要在Mapper文件下配置,首先我们要声明一个resultMap在查询之前,这个声明中要包含我们的列和属性的对应关系:
<resultmap id="UserMap" type="User">
<result column="id" property="id"><!--column表示列名,后面的property表示该列需要映射的属性名-->
<result column="name" property="name">
<result column="pwd" property="password">
</result></result></result></resultmap>
然后我们就可以在之后的配置中用resultMap替代resultType来实现返回值的映射。
<select id="getUserById" resultmap="UserMap" parametertype="int">
<!--与之前的设置相比,这里使用了resultMap指明返回映射关系-->
select * from Users where id = #{id}
</select>
四、Mybatis配置解析
Mybatis核心配置文档能涉及到的配置如下:
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
Tips:这里要注意,顺序是必须按照上面给出的这个表格中来的,如果顺序错误,会有错误提示The content of element type "configuration" must match(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)".
1、环境配置(environments)
MyBatis 可以配置成适应多种环境,不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
所以,如果想连接两个数据库,这显然需要两种环境,那么就需要创建两个SqlSessionFactory实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例,依此类推,记起来很简单:
- 每个数据库对应一个 SqlSessionFactory 实例
在核心配置文件中,environments后的default="..."中表示默认会使用哪种环境配置,其中的...则应该与之后你所配置的environment id="..."中的id相对应,这将表示你默认使用了你所配置的环境中的哪一个环境。
为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可。可以接受环境配置的两个方法签名是:
1.1、transactionManager(事务管理器)
Mybatis中有两种事物管理器,分别是JDBC和Managed,其中JDBC显而易见使用了JDBC的设置,而Managed则几乎没做什么,一般不常使用这个设置,只要默认为JDBC即可
1.2、dataSource(数据源)
数据源顾名思义指的就是数据库的连接,这个配置有三种,在正常情况下,默认使用有池的连接配置
UNPOOLED
POOLED(默认值)
JNDI
这里要先涉及到一个概念就是“池”,池是为了在web工程中提高连接数据库的效率而提出的概念,它的意义就是存在一个池使得数据库连接在用完以后可以不必立即关闭,而是先放在那里等待下一次连接再使用,是一个用完可回收的概念。
UNPOOLED就是指这个数据库环境配置下,任何数据库连接都不带有池,所有连接一旦使用完毕就被关闭,不会存放起来,所以在数据库连接请求较少的情况中是一种非常有效的配置,但在web项目中可能由于其访问量大而降低整体运行效率。
POOLED与之前的UNPOOLED相对,指的是在这种条件下,Mybatis使用有池的连接配置,所有连接在被用完后不会立即关闭,而是先放在一边等待下一个请求连接再度将其启用。在访问量较大的情况下能提升web响应效率,但也会占用一定的资源。
2、properties(属性)
我们可以通过properties属性来实现对配置文件的引用,即可以编写一个专门用于配置的文件xxxx.properties来应用于Mybatis的核心配置。
这些属性可以外部配置且可以动态替换。
比如我现在可以删除environment中的property字段,转而改为引用符
<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}">
</property></property></property></property></datasource>
</transactionmanager></environment>
</environments>
接下来配置一个database.properties文件如下:
driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
url=jdbc:sqlserver://localhost:1433;databaseName=Mybatis
username=sa
password=123456
再于核心配置文件的最前端添加properties外部引用如下:
<properties resource="database.properties">
这样同样是正确的!
但是,这里有一点必须要注意,properties字段中,同样可以直接添加property值,比如:
<properties resource="database.properties">
<property name="username" value="sa">
<property name="password" value="123456">
</property></property></properties>
在这种情况下,只要是外部文件中同样有的属性值,依然会优先使用外部配置文件中的值,一定要注意!
3、typeAliases(类型别名)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
typeAliase自定义别名
即在typeAliases标签下使用typeAliase来自定义别名,比如之前的Mapper文件:
<!--?xml version="1.0" encoding="UTF-8" ?-->
<mapper namespace="Dao.UserDao">
<select id="getUserList" resulttype="pojo.User">
select * from Users
</select>
</mapper>
可以看到这个pojo.User是非常冗长的一个东西,这里我们只写了一个mapper,如果写多个mapper都需要写全名,效率就会十分低下,解决的方法就是利用typeAliases在核心配置文件中设置一个对应的别名,如下:
<typealiases>
<typealiase type="pojo.User" aliase="User">
</typealiase></typealiases>
这样一来上面的文件就可以写成
<!--?xml version="1.0" encoding="UTF-8" ?-->
<mapper namespace="Dao.UserDao">
<select id="getUserList" resulttype="User"><!--此处User即为pojo.User的别名-->
select * from Users
</select>
</mapper>
package自动扫描定义别名
这种方式就是在typeAliases标签下使用package标签来自动扫描一个包下的所有类,然后在没有注解的情况下,这些类的别名会被指定为该类名的首字母小写版,比如上面这个例子,我们也可以这样写typeAliases:
<typealiases>
<package name="pojo">
</package></typealiases>
这样就会自动扫描pojo包下的所有类,从而使得这些类的别名都为该类名的首字母小写,在这个例子中,我的mapper就应该改为:
<!--?xml version="1.0" encoding="UTF-8" ?-->
<mapper namespace="Dao.UserDao">
<select id="getUserList" resulttype="user"><!--此处user即为pojo.User的别名,注意首字母要小写,因为自动扫描的别名不可diy-->
select * from Users
</select>
</mapper>
有注解的情况下,这些类的别名会取注解值,所以应该多使用package+注解的方式来实现别名,这样会使得代码更简洁
4、settings(设置)
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为,具体的设置细节可以去官网查看,总的来讲这个标签是为了实现一些具体设置的开关。
5、mappers(映射器)
该标签用于注册编写好的映射器mapper.xml文件,如下所示,有三种方式完成注册:
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml">
<mapper resource="org/mybatis/builder/BlogMapper.xml">
<mapper resource="org/mybatis/builder/PostMapper.xml">
</mapper></mapper></mapper></mappers>
使用资源引用是最安全的注册方式,以这种方式完成注册不会发生与映射器注册相关的错误,只是一定要注意资源引用要写全路径!
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper">
<mapper class="org.mybatis.builder.BlogMapper">
<mapper class="org.mybatis.builder.PostMapper">
</mapper></mapper></mapper></mappers>
使用类名而非mapper配置文件资源名注册有两个必须要注意的点:
- 接口类与mapper.xml文件必须同名
- 接口类文件必须与mapper.xml文件在同一个包下
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder">
</package></mappers>
使用包自动扫描的注意点与使用类名是一样的,必须要注意接口类与mapper要同名且位于一个包下
五、作用域与生命周期
这一节主要是关于Mybatis的生命周期与作用域的讨论
在高并发的情况下,一点点的资源浪费可能就会造成很大的问题,所以一个合适的生命周期和作用域对于代码中的每个变量和方法而言都是至关重要的。
SqlSessionFactoryBuilder()
很显然,该方法仅用于创建一个SqlSessionFactory对象,一旦我们获得了这个对象,该方法的生命周期也就应该随之走到尽头。
- 一旦创建了SqlSessionFactory对象就不再需要
- 应置于一个方法中,成为一个局部变量
SqlSessionFactory
该对象可以被想象为一个数据库的连接池,用于批量生成SqlSession,作为一个母体,一旦被创建就应当一直保留,在中途不应以任何理由抛弃或重新创建一个SqlSessionFactory对象。
- 一旦创建就要一直保留直到程序结束
- SqlSessionFactory的最佳作用域是应用作用域
- SqlSession应当使用单例模式或是静态单例模式
SqlSession
每个线程都应该有自己的SqlSession,它可以被当作连接到连接池的一个请求。
- SqlSession不是线程安全的,不能被共享,它的最佳作用域是请求或者方法作用域。
- 一旦使用完毕就应当立即关闭以释放资源,否则SqlSessionFactory作为连接池就无法继续提供SqlSession连接
六、日志
1、标准日志工厂
如果数据库操作出现了异常,就需要进行排错,这时如果有日志,就会对排错有很大的帮助!
在之前学习Mybatis配置解析时,提到了核心配置文件的settings标签下有很多内容,其中就包含我们所需要的东西:
logImpl:指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
其中所包含的有效值如下,本标签默认为未设置:
SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
比较重要的是LOG4J和STDOUT_LOGGING
设置非常简单,找到核心配置文件,在settings标签内添加如下:
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"><!--此值表示标准日志工厂实现,无需其他配置-->
</setting></settings>
这样Mybatis自带的日志工厂就会开始运作,先试试效果,可以看到之前简单的查询输出变成了下面这一大段内容:
Opening JDBC Connection
Created connection 2087885397.
Setting autocommit to false on JDBC Connection [ConnectionID:1 ClientConnectionId: 053f167b-dfaf-4c79-bf62-28113a8d925c]
==> Preparing: select * from Users
==> Parameters:
<== Columns: ID, NAME, PWD
<== Row: 1, WangWu, 666666
<== Row: 2, ZhangSan, 54321
<== Row: 3, LiHua, 67890
<== Total: 3
User{ID=1, NAME='WangWu', PWD='666666'}
User{ID=2, NAME='ZhangSan', PWD='54321'}
User{ID=3, NAME='LiHua', PWD='67890'}
Resetting autocommit to true on JDBC Connection [ConnectionID:1 ClientConnectionId: 053f167b-dfaf-4c79-bf62-28113a8d925c]
Closing JDBC Connection [ConnectionID:1 ClientConnectionId: 053f167b-dfaf-4c79-bf62-28113a8d925c]
Returned connection 2087885397 to pool.
这里面包含了Mybatis运作的详细细节,有了它,排查可能发生的错误将会更有效率。
2、log4J实现
首先,什么是log4J?
- Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件等等
- 可以通过它控制日志的输出格式
- 我们可以定义每一条日志的级别,以此实现更细致的日志控制过程
- 我们可以通过一个配置文件来灵活地进行配置,无需修改应用的代码
第一步要先导入包,在Maven中查找log4j依赖并导入就可以
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupid>log4j</groupid>
<artifactid>log4j</artifactid>
<version>1.2.17</version>
</dependency>
第二步是编写log4j的配置文件,这一步可以设置的东西可以说是非常非常之多,如果有这方面的需要可以上网查找,这里只写一个简单的配置文件
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file
#控制台输出的相关设置
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=[%c]-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/Mybatis.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
接下来就可以配置log4j为日志的实现了
<settings>
<setting name="logImpl" value="LOG4J">
</setting></settings>
实现后输出如下:
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 2088445230.
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Setting autocommit to false on JDBC Connection [ConnectionID:1 ClientConnectionId: 33224d70-811f-467b-96bb-95e8d4d94ce8]
[Dao.UserDao.getUserList]-==> Preparing: select * from Users
[Dao.UserDao.getUserList]-==> Parameters:
[Dao.UserDao.getUserList]-<== Total: 3
User{ID=1, NAME='WangWu', PWD='666666'}
User{ID=2, NAME='ZhangSan', PWD='54321'}
User{ID=3, NAME='LiHua', PWD='67890'}
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Resetting autocommit to true on JDBC Connection [ConnectionID:1 ClientConnectionId: 33224d70-811f-467b-96bb-95e8d4d94ce8]
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [ConnectionID:1 ClientConnectionId: 33224d70-811f-467b-96bb-95e8d4d94ce8]
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 2088445230 to pool.
此外,我们还可以在程序运行中自行添加一些日志语句,也就是利用Apache提供的包下的Logger类来实现在代码中自行生成一些日志语句。
首先要创建一个Logger对象,注意要用Apache提供的log4j包下的Logger类,这个对象应当与整个运行类绑定,所以作用域最好是类作用域,且整个类仅需要一个Logger对象。
Logger logger = Logger.getLogger(UserDaoTest.class);
有了这个对象以后,我们就可以通过下面这些方法来生成额外的对应级别的日志:
logger.info("...");//生成info级别的日志
logger.debug("...");//生成debug级别的日志
logger.error("...");//生成error级别的日志
七、分页
分页的意义是减少数据的处理量。
1、Limit实现分页
首先复习一下sql中使用limit来进行分页操作:Tips:此操作SqlServer不支持,所以不要在SqlServer中使用,否则会报错
# 语法:select * from table limit startindex,pagesize;
select * from User limit 0,2;#这就表示从User表中提取全部信息从0开始,2行一页
那么接下来就是一个标准的多数据查询了,利用map类作为输入,提取startIndex和pagesize两个值,最终完成分页操作。
上面这种方法本质上使用了sql来完成分页操作,Mybatis只是传达了信息而已,下面还有一种利用对象来完成分页的操作,这就是RowBounds
2、RowBounds实现分页
RowBounds实现分页要用到之前没有讲到的直接用sqlSession调用sql语句的方法来实现,即直接用sqlSession下的selectList()方法获取查询结果,在调用这个方法时,我们可以输入一个RowBounds对象来实现分页,那么首先创建一个RowBounds对象:
RowBounds rowBounds = new RowBounds(int offset,int limit);
之后调用方法获得结果:
List<user> userList = sqlSession.selectList("Dao.UserDao.getUserList",null,rowBounds);
就可以得到分页的结果集了。
3、分页插件pageHelper
了解即可,若要使用可以去百度。
八、使用注解开发
在实际开发中,我们很多时候会从面向对象进行开发转为面向接口进行开发,这么做的原因是为了
解耦,可拓展,提高复用性,在分层开发中上层无需关心下层的实现,提升规范性
其中最最关键的就是解耦 这个关键词
对于接口要有如下理解
- 接口本质上就是定义(规范与约束)与实现(具体流程)的分离
- 接口本身反映了设计者对于系统的理解
- 接口本质上有两种
- 一种是针对一个个体的抽象
- 一种是针对一个个体某一方面的抽象
- 一个个体可能有多个抽象面
对于Mybatis来讲,使用注解进行开发有两步
- 在接口类进行注解,注解内容为本来在Mapper.xml文件中配置的sql语句:
@Select("select * from Users")
List<user> getUserList();
//这里有些问题要注意,在配置接口的时候如果遇到需要有多个基本类型的参数,就最好在参数前添加该参数的映射注解,如下
@Select("select * from Users where id = #{id} and name = #{name}")
User getUserById(@param("id") int id,@param("name") String name);
Tips:方法若存在多个基础类型或String参数,所有参数前面必须加上@param("...")注解,该注解的意义就是将参数标记为对应名称的引用值
Tips:这里还有个问题就是记不记得增删改这三个操作是需要提交事务的,这里最好在获取SqlSession连接时,在openSession()方法中写上一个true表示本SqlSession中所有Mapper操作皆自动提交事务
- 在核心配置文件中绑定接口类
<mappers>
<mapper class="Dao/UserDao">
</mapper></mappers>
接着就可以使用了。到这里,基础的数据库的增删改查已经不足为惧了。
注解开发的本质是反射机制的实现
底层是动态代理
九、Mybatis执行流程
1、表面上的Mybatis执行流程
本节具体分析了Mybatis的具体执行流程。
Mybatis纵观下来,其可见的整体执行流程可以概述为以下步骤:
- 编写核心配置文件,利用Resources类加载配置文件为数据流
- 通过SqlSessionFactoryBuilder()方法解析配置文件,根据文件生成一个SqlSessionFactory对象,这个对象可以被视为一个连接池,该对象保有配置文件中的全部信息,并且通过反射机制,同样抱有注册了的Mapper接口及接口配置的全部信息。
- 由SqlSessionFactory对象的openSession()方法返回一个SqlSession对象,该对象可以被视为一个连接
- 通过SqlSession获得Mapper接口类对象,即可通过接口类调用方法获取结果,完成JDBC操作
但是!上面的这几个步骤只是一个表面现象,在表面现象之下还必须要了解其具体的执行步骤!
2、真实流程
- 首先依然是编写核心配置文件,由Resources类的方法将其加载为输入流
- 此时,我们调用SqlSessionFactoryBuilder()方法,希望获得一个SqlSessionFactory对象,在这里,Mybatis实际上先创建了一个SqlSessionFactoryBuilder构造器对象,该对象的构造函数创建了一个XMLConfigBuilder对象,该对象通过接受刚刚Resources类加载的输入流,解析了核心配置文件中的内容,再根据这些内容调用该解析器类的parse()方法最终创建了一个SqlSessionFactory对象并返回
- 于是我们得到了SqlSessionFactory的实例化对象,通过该对象,我们可以获得我们所需要的SqlSession对象,这两个对象正如前面所说可以被视为连接池和连接对象。但是在获取SqlSession之前,transactionManager事物管理器已经根据我们之前配置的type构造完毕。如果我们配置的是JDBC,那么这里就是用Java自带的Connection类来完成事务的提交,回滚等操作。
- 有了事物管理器,就可以创建执行器了,执行器是SqlSession的功能,有了执行器,我们就可以获取一个SqlSession对象,并通过SqlSession对象来调用Mapper接口类实现数据库的增删改查了,而在实现增删改查的过程中发生任何问题,那么就需要回滚,这里就又会回到transactionManger那里去,如果执行成功,那么提交事务并关闭就完成了Mybatis的一次执行。
九、复杂关系的查询
1、何为复杂关系
所谓复杂,自然是与简单相对应,对于数据库而言,复杂,就是指表与表之间的关系复杂,查询结果可能需要关联多张表的多个结果来产生,这就是复杂查询。
在Mybatis中,这种查询最大的复杂之处在于很可能查询的结果不止要返回一个类的对象,往往该类对象中存在属性是其他复杂类,比如:
在老师与学生的关系中,可能学生会存在一个属性,这个属性表示该学生的老师是谁,那么这个属性实际上是一个老师类的对象,所以在查询学生时,除了要返回学生类的基本属性,还需要返回一个老师类的对象!这种情况进阶按照上面的知识是无法处理的。
2、多对一的处理
所谓多对一,就是说在查询结果中,有多个元组的某个结果要对应另一个表中的单一元组,在Mybatis中,这种情况有两种处理办法可以使用:
第一种:按结果嵌套处理
顾名思义,首先用sql语句查询表中所需要的内容,最后在返回结果时,通过resultMap来进行类的嵌套。所以在Mapper.xml文件中我们应该先写查询:
<select id="getStudentList2" resultmap="StudentMap">
select s.id sid,s.name sname,t.id tid,t.name tname
from students s,teachers t
where s.tid = t.id
</select>
可以看到这个查询会查到两个表中的内容,包括students表中的id和name以及teacher表中的id和name,查询条件是students中的tid等于teacher的id。
接着就是问题了,我们需要查询的结果是一个Student类,这个类的属性有id,name以及teacher,其中teacher是个类,所以我们的返回形式必须用resultMap标签来自定义一个StudentMap,否则直接返回Student类的话,一定会显示teacher属性为null。
所以我们自定义一个resultMap:
<resultmap id="StudentMap" type="Student">
<result column="sid" property="id">
<result column="sname" property="name">
<association property="teacher" javatype="Teacher">
<result property="id" column="tid">
<result property="name" column="tname">
</result></result></association>
</result></result></resultmap>
这里引入了新的要素:association标签,该标签的作用是在结果集映射中关联一个类,在该标签下可以设置关联类属性和查询语句属性之间的关联,设置方法与普通的结果集映射方法一致。
第二种:按查询嵌套处理
上面一种的思路是,先用sql查询我们所需要的值,再利用这些值对结果进行处理,将之变成我们想要的模样。
那么这一种的思路就是我直接嵌套一个查询,直接查出我们所想要的东西。我们先定义两个查询:
<select id="getTeacherById" resulttype="Teacher">
select * from teachers where id = #{tid}
</select>
<!--上面是一个简单的通过id查询teacher,下面是查找所有表中的student-->
<select id="getStudentList" resultmap="StudentMap">
select * from students
</select>
当然了,这里的第二个查询如果就这么写,一定会报错,因为StudentMap还没有定义,而如果直接用resultType="Student"则会在teacher这个属性上显示为null,那么这里要怎么定义resultMap呢?
<resultmap id="StudentMap" type="Student">
<association property="teacher" column="tid" javatype="Teacher" select="getTeacherById">
</association></resultmap>
与前面的通过结果嵌套不同,这里我们要通过查询来进行嵌套,这里不用设置association的其他属性,直接在该标签那一行来设置。
设置思路是,首先这里是一个student的查询,查询结果要依赖tid来查询teacher,那么就明晰了,首先属性是teacher,查询结果列是tid,返回形式是Teacher类,查询方法是刚刚定义的getTeacherById即利用id查询teacher。
总结一下:其实可以看出,这两种处理方式就对应了Sql里的联表查询和子查询,总体而言还是联表查询的结果嵌套方式更为简洁
3、一对多的处理
所谓一对多,就是指一个查询目标关联了多个附属目标的情况,比如之前举的老师与学生的例子中,多对一就是指查询的多个学生需要关联到一个老师对象上。而多对一就是反过来,查询一个老师时需要关联至多个学生。在Mybatis中,同样还是那两种策略可用:
第一种、按结果嵌套处理
这里先认识一下另外一个疏于resultMap的标签:collection,collection表示集合,意思是被标注的属性为一个集合的映射,该标签下使用结果嵌套时要注意一点:在collection标签下声明集合的类型时需要使用ofType="..."而非javaType="..."这个如果使用错误会导致报错。
那么首先这里先创建一个查询,该查询还是一样,需要利用sql一次性查询出我们所要的信息。
<select id="getTeacherById" resultmap="TeacherMap">
select t.id tid,t.name tname,s.id sid,s.name sname
from teachers t,students s
where s.tid=t.id and t.id = #{id}
</select>
这样我们就直接获得了必须的teacher的id和name,以及该老师的所有学生的id和name。
现在有了数据,就需要利用数据来组成结果了,结果是一组teacher属性与多组student属性的嵌套,如下:
<resultmap id="TeacherMap" type="Teacher">
<result property="id" column="tid">
<result property="name" column="tname">
<!--上面是teacher属性的声明,这里一定要做,不然的话id会显示为初始值0,也就是无法自动分辨那么多id究竟属于谁-->
<collection property="studentList" oftype="Student"><!--这里必须是ofType="..."-->
<result property="id" column="sid">
<result property="name" column="sname">
</result></result></collection>
</result></result></resultmap>
这是一种方法。
第二种、按查询嵌套处理
与之前的多对一查询嵌套思想是一样的,结果嵌套是复杂的sql查询出多个结果,然后resultMap配置处理结果。而查询嵌套就是简单的sql查询结果,但是需要多重查询嵌套执行才能获取完整结果。首先创建两个查询:
<select id="getTeacherById" resultmap="TeachersMap">
select * from teachers where id = #{id}
</select>
<select id="getStudents" resulttype="Student">
select * from students where tid = #{id};
</select>
很明显,我们先进行老师的查询,获取了老师的id以后,再利用id值进行嵌套查询来找出所有的学生。那么resultMap配置如下:
<resultmap id="TeachersMap" type="Teacher">
<result property="id" column="id">
<result property="name" column="name">
<collection property="studentList" column="id" javatype="ArrayList" oftype="Student" select="getStudents">
<!--注意,这里的类型设定就不能只是ofType了,同时因为我们要返回多个值,应当使用List来作为类型-->
</collection></result></result></resultmap>
4、复杂查询总结
- 复杂查询有两种情况:多对一,一对多对应association(关联)和collection(集合)两种关键resultMap标签
- 复杂查询有两种思路:结果嵌套,查询嵌套
- 结果嵌套就是先用复杂的sql查询出我们需要的所有结果,然后在resultMap配置中对结果进行整理
- 查询嵌套就是先编写简单的sql语句用于查询我们所需要的结果,然后在resultMap配置中嵌套查询以获取最终结果
十、动态SQL
有时,我们在进行sql语句查找的时候,会需要根据输入动态的选择需要使用的sql语句,比如如果有一个博客网站,我希望实现如下需求:
当我什么也不输入,直接查询博客时返回所有博客,当我输入id时根据id查找,当我输入名称时根据名称查找,输入多个条件时则综合查找
那么显然之前所学的内容就不足以完成这项工作了,此时我们面临两个选择,第一是重载Mapper中的接口函数,然后在mapper.xml文件中配置数个与不同输入相对应的查询文档,但是这显然费时费力,有没有更好的方法来处理呢?这就需要引入Mybatis的动态sql了。
1、if,where语句
if标签
此处引入if这个标签作为动态sql的开始,此标签需要配置于mapper.xml文件下,更具体来说是mapper.xml文件中的select等标签内部,它的作用是根据你的输入不同,可以使得if中的内容额外的配置进sql中,这么说很抽象,举个例子:
<select id="getBlogList" parametertype="map" resulttype="Blog">
select * from Blogs where 1=1<!--忽略这个奇怪的where 1=1,此处是为了后面的另一个关键字留白-->
and id = #{id}
and author = #{author}
</select>
上面这就是一个动态sql语句,其作用是输入内容中如果没有id和author信息,就从Blogs表中查询所有内容并返回。
如果有id信息,那么添加and id = #{id}
进入原来的sql语句末端,从而使得查询发生变化。
如果有author信息,那么添加and author = #{author}
进入原来的sql语句末端,同样使查询发生变化。当然,你也可以既有id也有author,这样查询就会有两个关键字了。
where标签
但是无论如何,上面的sql中的where 1=1
还是太奇怪了,但是问题在于如果不写这么一句在那里,那么select * from Blogs and id = #{id}
显然是错的,但是如果把where写进if里,又会造成如果同时输入了id和author语句会成为select * from Blogs where id = #{id} where author = #{author}
的尴尬境况,所以这个语句想要正常运行,这个无厘头的where 1=1
却又必不可少,所以Mybatis引入了这个标签:where
这个标签的意义在于,当你使用动态if语句时,显然你会需要一个where在sql语句里,那么你可以在mapper.xml中这样配置where标签来避免写where 1=1
这样的垃圾代码。
<select id="getBlogList" parametertype="map" resulttype="Blog">
select * from Blogs
id = #{id}
and author = #{author}
</select>
where即可以作为一个条件查询的开头标签使用,该标签只有在下面的条件查询条件至少满足其一时才会启用并向原来的sql语句中插入where语句,同时如果if的内容中有不必要的"and"或是"or"语句,该标签会自动将其删除
比如上面这个mapper查询,如果无输入,就会查询select * from Blogs
如果存在id信息输入,则为select * from Blogs where id = #{id}
如果有author信息,则为select * from Blogs where author = #{author}
如果二者兼具,则为select * from Blogs where id = #{id} and author = #{author}
2、choose,when,otherwise语句
与上面的if,where语句相比,这三个标签适用于你仅仅只需要用到额外的一个sql语句,而非可能需要用到所有的额外的sql语句的情况下。这一特点可以类比于switch的用法。
choose用法类似于switch,用于声明后面会出现一个选择情况,该选择由若干个when以及最后的一个otherwise组成,但是这种方法一次执行且仅执行其中的一个语句,如果所有条件都满足,就会按顺序执行第一个语句而非全部执行。例如:
<select id="getBlogList" parametertype="map" resulttype="Blog">
select * from Blogs
where id = #{id}
where author = #{author}
where views > 5000
</select>
上面这个例子中,我们有三种情况,分别是:有id,有author和什么都没有。此时:
如果无输入,就会查询select * from Blogs where views > 5000
如果存在id信息输入,则为select * from Blogs where id = #{id}
如果有author信息,则为select * from Blogs where author = #{author}
如果二者兼具,则依然为select * from Blogs where id = #{id}
因为使用choose关键字时,是不会执行一条以上的额外语句的。
3、if,set语句
前面的两种情况都是针对查询的时候的解决方案,这里的set,if则是针对更新数据库时可能遇到的问题,前面讲where的时候说到如果没有where标签,很多时候你就不得不写一个永真的where 1=1
凑在这里,因为如果没有这个的话,很多sql语句都会有语法上的错误。那么同理,set标签就是用来解决在更新数据库时的set的问题的。我们都知道,一个正常的更新sql语句为:
update Blogs set name = #{name} where id = #{id}
如果没有set标签,那么这里的动态语句会写的非常尴尬:
<update id="updateBlogs" parametertype="map">
update Blogs set
<if test="name != null">
name = #{name},
</if>
<if test="author != null">
author = #{author}
</if>
where id = #{id}
</update>
这个update Blogs set
后面很明显是会有问题的,如果只满足了name != null
那么sql语句会变成update Blogs set name = #{name}, where id = #{id}
可以看到句子中多了一个毫无意义的','从而影响了句子的正确性,这种微小却又致命的错误很容易打击到我们的自信。但有了set标签,事情就完全不一样了。利用set标签,Mybatis会自动帮我们动态的前置set,并且判断是否有不需要的','继而保证其sql语句的正确性。
下面就是一个正确的实例
<update id="updateBlogs" parametertype="map">
update Blogs
<set>
<if test="name != null">
name = #{name},
</if>
<if test="author != null">
author = #{author},
</if>
</set>
where id = #{id}
</update>
4、trim(set,where)
之前谈到了set和where标签的妙用,但是可以看出set和where是有一定的规律的,这个规律就是这两个标签都是前置一个sql语句,同时自动地处理标签内部的某个多余的字符,比如"and | or"和","这是因为这两个标签本质上都是来自于同一个trim标签。
trim标签有两个关键字:prefix和suffixOverrides,这两个标签就分别对应了前置内容和自动忽略设置,比如where本质上就是:
<trim prefix="where" suffixoverrides="AND |OR">
...
</trim>
而set就是
<trim prefix="set" suffixoverrides=",">
...
</trim>
了解了这一点以后,我们其实就可以利用trim来自定义相关的关键字了。
5、foreach语句
foreach语句主要针对的是需要通过一个集合来进行遍历数据库的情况,foreach最好用于sql中in的后面,要想会用foreach,首先要明白它到底是什么东西。
本质上foreach就是一个特别的拼接sql语句的标签,它有如下附属标签:
- collection:表示foreach需要从哪个集合中取内容
- item:表示取出的内容的名字,在foreach标签下可以通过#{}引用该名字,从而引用集合中的所有内容
- open:表示需要拼接的sql语句的开头字段
- close:表示需要拼接的sql语句的结尾字段
- separate:表示各个集合元素应该由怎样的分隔符分割
- index:表示一共要从集合中按顺序取多少次元素
有了上面这些概念,下面就可以理解一下这个实例从而掌握foreach的用法了:
<select id="getBlogForeach" parametertype="map" resulttype="Blog">
select * from Blogs
id =#{id}
</select>
我们想要按照id遍历Blogs表中的数个元素,那么这里首先是一句标准的select * from Blogs
然后接where标签,因为它非常智能,实际上在Mybatis中可以完美替代where,接着就是foreach语句,我们需要的效果是:
select * from Blogs where (id = id1 or id = id2 or id = id3)
所以foreach就需要拼接(id = id1 or id = id2 or id = id3)
这一段,那么根据上面的概念。
collection需要是id的集合
item为id
open为(
这一段,我们可以写成and (
因为where标签会帮我们去除不必要的and
close为)
separate为or
内容显然是id = #{id}
于是最终我们就获得了上面实例中的代码,这将可以实现上面那句sql的效果,只要我们在外面通过map输入任意个id的集合,那么就可以直接根据这些id进行Blogs表的遍历了。
总结:动态SQL实际上就是在拼接SQL语句,如果不知道该怎么写动态SQL,最好的方法就是先写出完整的SQL,然后逆向分析如何拼接才能完成
十一、缓存
1、什么是缓存
缓存概念的核心理念是效率,连接数据库并执行查询返回结果,这个动作是个费时费力的过程,有时候我们希望会有一些方法,可以帮助我们改进这个过程。缓存正是为了这件事而生,根据集中性原理,当我们执行了某个查询以后,该查询结果我们大概率短时间内会再度查询,那么何不在查询之后将内容暂存于某处,方便下次再度查询时返回呢?
这个思想所引出的正是缓存,缓存,就是把查询返回出的结果暂时存放于内存中,方便短时间内下次取用。
在计算机概念中,缓存(Cache)就是
- 存在内存中的临时数据
- 将用户经常查询的数据放在内存中,这样就可以避免反复与低速的外存打交道,从而大大提升效率
为什么数据库系统要引入缓存?
- 与计算机一样,为了避免反复与低速的数据库进行连接,查询,断开的操作,从而提升效率
缓存适合怎样的数据使用呢?
- 适合经常会被查询,但又并不经常发生改变的数据使用
2、Mybatis与缓存
Mybatis内置了一个非常强大的缓存特性,它可以非常方便的配置和定制缓存,从而极大地提升查询的效率。
Mybatis系统中默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下仅仅开启一级缓存(SqlSession级别的缓存,又称本地缓存)它仅仅对一个会话中的数据进行缓存
- 二级缓存需要手动开启和配置(基于namespace级别的缓存)它可以对全局数据进行缓存
- Mybatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义缓存
一级缓存
一级缓存默认开启,仅于一次SqlSession中有效,无法配置所以比较简单,这里只需要了解它的作用及实效条件
一级缓存失效条件:
- 查询不同的东西
- 进行增删改操作
Ps.这一点带来一个有趣的现象,当我们连续查询两次同一个元组时,如果我们分别保存对象并用==
来判断二者,返回的结果会是true
但是如果我们先查询该元组,再修改表中另一个元组,最后在查询原来的元组,然后再用==
进行对比,你会发现二者虽然并无区别,但是返回结果会是false
这就证明前后统一元组的缓存地址确实已经被刷新了。 - 查询其他的Mapper.xml
- 手动清理缓存
即调用sqlSession.clearCache()方法手动清理缓存
二级缓存
二级缓存是针对一个Mapper.xml文件都会生效的缓存,由于一级缓存作用域很小,实用价值不大,二级缓存基于namespace级别,一个名称空间共用一个二级缓存。
二级缓存工作机制:
- 当一个会话查询某条语句,默认的一级缓存就会把查询结果保存
- 若会话关闭,则一级缓存就会消失,此时一级缓存的内容就被保存至二级缓存当中,便于下个会话启动后查询使用
要开启它仅仅只需要在Mapper.xml中添加一行
<cache>
可以说是非常简单了。此外这里还有些标签可以调整缓存的参数,包括:
- eviction:表示缓存调度策略
- flushInterval:表示最长多久刷新一次缓存,单位为ms
- size:缓存大小,单位是个引用
- readOnly:是否只读
小结:
- 只要开启了二级缓存,在同一个Mapper下即有效(命名空间为基础)
- 所有数据都会先存入一级缓存,一级缓存消灭后转存入二级缓存
- 只有当会话提交或关闭时,数据才会转存入二级缓存中
3、Mybatis缓存原理
![Mybatis缓存原理图](C:\Users\24779\Pictures\Saved Pictures\Mybatis缓存原理图.png)
上图为Mybatis的缓存原理示意图,核心在于,当用户提交一个查询时,是一个从上到下的流程:
- 当走到Mapper(命名空间)这一级别时,就回去查看二级缓存有没有查询所需要的结果内容,如果有所需内容就直接返回
- 如果没有,就向下走一层到SqlSession这一级别来,先看看SqlSession自带的一级缓存中有没有我们所需要的内容,如果有就返回
- 还是没有就通过SqlSession对数据库进行查询并且把查询结果保存至一级缓存中
- 当SqlSession关闭时,将一级缓存的内容保存至二级缓存中
这就是Mybatis缓存系统的一个整体流程
额外内容一、Lombok开发插件
Lombok是一个插件,用于简化开发代码,使得某些代码无需写出,可以直接用注解来代替。Lombok目前的主要注解如下:
@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//包含了无参构造,getter,setter,toString,hashCode,equals这些实体类应有的方法
//*********************下面则是一些零散的注解方法******************************************************************************
@AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor//这些都涉及到构造函数的具体配置,All表示全参构造,No表示无参
//下面这些的作用显而易见了
@Getter and @Setter
@ToString
@EqualsAndHashCode
//****************************************************************************************************************************
```</cache></user></user></properties></user></user></user>