什么是mybatis?
从官方文档我们可以了解到,MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
也就是说,mybatis简化了我们对数据库操作的步骤,不用再像以前一样通过jdbc来操作数据库。
第一个mybatis程序
首先,在编写mybatis程序前要写mybatis的核心文件----->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>
<!--dafault值得是默认的环境,也就是指向默认的id-->
<environments default="development">
<environment id="development">
<!--事务管理器-->
<transactionManager type="JDBC"/>
<!--这些就是数据库的信息了,url,drive等等-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/study?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="267946"/>
</dataSource>
</environment>
</environments>
<!--mapper注册-->
<mappers>
<mapper resource="UserMapper.xml"/>
</mappers>
</configuration>
第二步编写mybatis工具类获取SqlSession。在mybatisutils中通过SqlSessionFactorBuilder获取SqlSessionFactoty,然后通过SqlSessionFactory获取Sqlsession。这也是这三者之间的关系。
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.*;
import java.io.*;
public class MybatisUtils {
static SqlSessionFactory sqlSessionFactory;
static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//第一步,获取SqlSessionFactory对象
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//有了SqlSessionFactory对象就可以获取SqlSession对象
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
第三步:编写代码,编写实体类与操作实体类的接口。
mybatis简便之处就是不用再写接口的实现类,通过一个xml配置文件或注解即可,这个xml文件也就是mybatis的魔力
User类就不再创建了,这里只说一下属性,id,name,age
Dao接口:
import com.study.Pojo.User;
import java.util.List;
public interface UserDao {
//查询数据库中的数据
public List<User> getUserList();
}
mapper.xml文件配置要放在与它对应的类的包下,我们要在pom.xml文件中加上一段配置。除此之外,还要在mybatis-config中进行mapper注册,不然也会出错
这是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接口-->
<mapper namespace="com.study.dao.UserDao">
<!--通过id绑定接口中的方法,resultType指定返回值中的泛型(查询中有resultType),除此之外,resultType中写全类名-->
<select id="getUserList" resultType="com.sutudu.Pojo.User">
select * from t_user ;
</select>
</mapper>
这是在导出失败的时候加的一段代码,
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
接下来我们就可以进行使用了。
import com.study.Pojo.User;
import com.study.Utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class TsetUserDao {
@Test
public void test(){
//通过工具类获取SqlSession
SqlSession sqlSession = MybatisUtils.getSqlSession();
//要调用方法首先要得到对象
//通过映射获取UserDao
UserDao mapper = sqlSession.getMapper(UserDao.class);
List<User> userList = mapper.getUserList();
for (User user : userList){
System.out.println(user);
}
sqlSession.close();
}
}
实现简单的CRUD
在做完第一个Mybatis程序后,就要来实现一下简单的CRUD,肯定会有不全面的部分,这个后面会学到的,所以稍安勿躁。
在UserMapper接口中编写增删查改的方法,然后在UserMapper.xml配置文件中通过绑定id等参数来绑定UserMapper接口中的方法。
UserMapper.xml文件中的配置
万能Map:
使用map可以让我们对程序的开发更加灵活,而且当传递多个参数的时候使用map可以达到我们想要的结果。
map传递参数: 在使用map前,首先要将parameterType改为map,然后在调用的时候直接#{key}即可。
对象传递参数: 使用对象传递参数,将parameterType改为对应的类型,然后在调用的时候#{对象属性}即可。
只有一个基本类型的情况: 当只有一个基本类型的时候,直接在sql中调用即可。
就比如说你要查询id=1并且name=张三的人的资料,使用map即可。也可以在方法中写上注解@Param(“属性名”)
测试代码:
模糊查询:
模糊查询有两种方式,一种是用户传递通配符%…%,另一种方式就是在sql语句中写上%#{value}%,就例如**(select * from t_user where name like %#{value}%)**
配置及属性优化
环境配置:
POLLED:表示有池子。UNPOLLED:没有池子。
池子:创建多个连接,使用时调用即可,不用时放在池子里面等待下一个连接。
properties(属性)
这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。例如:
driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/study?serverTimezone=UTC&useSSL=true&useUnicode=true&characterEncoding=UTF-8"
username=root
password=267946
然后在mybatis-config中使用properties标签进行引入
设置好的属性可以在整个配置文件中用来替换需要动态配置的属性值。比如:
<environments default="development">
<environment id="development">
<!--事务管理器-->
<transactionManager type="JDBC"/>
<!--这些就是数据库的信息了,url,drive等等-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
类型别名(typeAliases):
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
有两种书写方式,一种是扫描类,例如:
<typeAliases>
<!--type是你指定的那个对象,alias是你给类起的别名-->
<typeAlias type="com.study.Pojo.User" alias="User"/>
</typeAliases>
然后在你的mapper.xml中就不写全类名了,直接写别名即可,例如:
<select id="getUserList" resultType="User">
select * from study.t_user
</select>
另一种方式是扫描包:
<typeAliases>
<package name="com.study.Pojo"/>
</typeAliases>
mybatis文档中说了,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。所以我们在写类的全类名的时候写类名的小写即可。
注意:
在实体类较少的时候我们可以用第一种方式,实体类较多的时候我们使用第二种方式。另外也可以通过注解给类起别名,方法是在类上面使用@Alias(“起的别名”)。
设置(settings)
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。
在mybatis官方文档我们可以查找到。
映射器(mappers)
既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等。例如:
<mappers>
<!--使用相对于类路径的资源引用-->
<mapper resource="com/study/dao/UserMapper.xml"/>
<!--使用映射器接口实现类的完全限定类名-->
<mapper class="com.study.dao.UserMapper"/>
<!--将包内的映射器接口全部注册为映射器-->
<package name="com.study.dao"/>
</mappers>
这三种方式推荐使用第一种。
注意:
第二第三种方式接口和它的Mapper配置文件必须同名,并且Mapper配置文件必须和接口在同一个包下。
resultMap结果映射
在我们实际开发中可能会有数据库字段名与实体类中的属性名不相同的情况,遇到这种情况我们有两种处理方式,一种是最简单最暴力的方法,就是在sql语句中为表中的字段起别名,比如:
假设实体类中的属性为id,name,password,数据库中的字段为id,name,pwd,那么在sql语句中就可以为pwd字段起别名
select id,name,pwd as password from t_user;
但是这种方法在属性过多或者属性为对象的时候就不能使用了,这时候就要使用到结果映射也就是resultMap。
- resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。
- 在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同等功能的数千行代码。
- ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
先来看一个简单的结果映射,在这个实体类中没有对象等类型。
<!--id可以用来表名resultMap指向哪一个,type表示返回的类型-->
<resultMap id="result" type="User">
<!--子元素id代表resultMap的主键,而result代表其属性。-->
<id property="id" column="id"></id>
<!--property表示类中的属性名,column表示表中的字段名-->
<result property="t_name" column="name"></result>
<result property="age" column="age"></result>
</resultMap>
<select id="getUserList" resultMap="result">
select * from study.t_user
</select>
如果表中的字段名与实体类中属性名一致则可以不用进行映射。
日志
如果一个数据库操作出现了错误,我们需要拍错,这时,日志就是最好的助手。
要用到日志,需要在核心配置文件中进行配置,因为logImpl没有默认值。
在mybatis中使用哪一个日志实现,需要进行设置!
STDOUT_LOGGING标准日志输出
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
在使用日志之后,会输出很多信息,截取一部分作为解读。
LOG4J
什么是log4j?
- Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等
- 我们也可以控制每一条日志的输出格式
- 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程
- 最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
第一步:导包
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
第二步:编写properties文件,也就是配置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/mycode.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
第三步:在mybatis核心配置文件中进行配置
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
第四步:测试,并在控制台输出
日志对象的简单实用:
日志对象有很多等级,主要的有三种,info,debug,error
static Logger logger = Logger.getLogger(TsetUserDao.class);
@Test
public void testLog4j(){
logger.info("info");
logger.debug("debug");
logger.error("error");
}
输出结果为:
在日志文件中也会相应的输出等级
[INFO][2021-07-05][com.study.dao.TsetUserDao]info
[DEBUG][2021-07-05][com.study.dao.TsetUserDao]debug
[ERROR][2021-07-05][com.study.dao.TsetUserDao]error
分页
使用分页可以减少我们数据的处理量,减少呈现在我们面前的数据,让我们更方便去处理这些数据。
使用limit分页
sql分页语句:select * from tableName limit startIndex,pageSize;
分页在mybatis中的使用:
1:在mapper类中定义方法,写明参数以及返回值
List<User> getUserByLimit(Map<String,Integer> map);
2:在对应的mapper.xml文件中写上sql语句
<select id="getUserByLimit" resultType="user" parameterType="map">
select * from t_user limit #{startIndex},#{pageSize}
</select>
3:在测试类中进行测试
@Test
public void testLimit(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String,Integer> map = new HashMap<>();
map.put("startIndex",0);
map.put("pageSize",2);
List<User> userList = mapper.getUserByLimit(map);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
结果:
使用RowBounds分页(了解)
接口:
List<User> getUserRowbounds();
mapper.xml文件
<select id="getUserRowbounds" resultType="user">
select * from t_user
</select>
测试:
@Test
public void getUserRowbounds(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
RowBounds rowBounds = new RowBounds(0,2);
List<User> userList = sqlSession.selectList("com.study.dao.UserMapper.getUserRowbounds", null, rowBounds);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
使用注解进行开发
使用注解来映射简单语句会使代码显得更加简洁,但要注意对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
第一步:在接口方法上写上注解,并在注解中写上你的sql语句
第二步:绑定mapper
第三步:进行测试
本质:使用反射机制
底层:动态代理
简单CRUD
public interface UserMapper {
@Select("select * from t_user where id = #{id}")
public User select(@Param("id") int id);
@Insert("insert into t_user values(#{id},#{name},#{age})")
public void insert(User user);
@Update("update t_user set name = #{name},age = #{age} where id = #{id}")
public void update(User user);
@Delete("delete from t_user where id = #{id}")
public void delete(int id);
}
将接口加载到核心配置文件中,然后测试即可。
关于@Param()注解
- 基本类型的参数或者String类型的参数需要加上
- 引用类型不需要加
- 如果只有一个基本类型的话,建议加上
- 我们sql中使用到的是我们在注解中起的名字,而不是参数名
mybatis实现复杂查询
准备步骤:
写明两个对象,Teacher和Student,Student中包含Teacher属性,编写Student和Teacher接口,编写mapper.xml文件然后进行注册。
多对一处理
要求:查询学生信息并且查出他的老师的信息。
原sql语句:select sid,sname , tname from t_student s, t_teacher t where s.tid = t.tid;
按照查询嵌套处理:
按照结果嵌套处理
一对多处理:
按结果嵌套查询:
这里查询老师以及他的学生们,类中使用list集合。
老师类:
学生类:
这是mapper.xml中对这条sql语句的配置
<select id="selectTeacher" resultMap="selectStudentTeacher">
select t.tid tid ,t.tname tname,s.sid sid ,s.sname sname
from t_teacher t,t_student s
where t.tid=#{tid} and s.tid = t.tid
</select>
<!--
复杂的属性,我们需要单独处理,对象使用association,集合使用collection,集合中的泛型信息我们使用ofType
-->
<resultMap id="selectStudentTeacher" type="Teacher">
<result property="tid" column="tid"/>
<result property="tname" column="tname"/>
<collection property="students" ofType="Student">
<result property="sid" column="sid"/>
<result property="sname" column="sname"/>
</collection>
</resultMap>
按照查询嵌套处理:
同上面的查询条件一样,首先我们需要查询老师的信息,由于老师类中有list集合,所以我们要使用resultMap进行映射,在resultMap中设置标签,对于表中的属性名和字段中的字段名(sql语句中有别名与别名对比)作匹配,对于集合使用collection,
javaType对应java类型,ofType对应泛型。
子查询中的参数通过column传递。当有多个参数时,可以写成column="{prop1=col1,prop2=col2}",前者为你的子串参数名,后者是你给赋的值。
<select id="selectTeacher" resultMap="selectTeacherById">
select * from t_teacher where tid = #{tid}
</select>
<resultMap id="selectTeacherById" type="Teacher">
<result property="tid" column="tid"/>
<result property="tname" column="tname"/>
<collection property="students" javaType="ArrayList" ofType="Student" select="selectStudentByTeacherId" column="tid"/>
</resultMap>
<select id="selectStudentByTeacherId" resultType="Student">
select * from t_student where tid = #{tid}
</select>
动态sql
什么是动态sql?
动态sql就是根据不同条件生成不同的sql语句。在mybatis中有四种元素种类给我们操作。
if
使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分,比如:
<select id="getBlogIf" parameterType="map" resultType="blog">
select * from blog where 1=1
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</select>
这条语句可以根据你是否输入title和author来输出不同的结果。
choose(when,otherwise)
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
<select id="getBlogChoose" parameterType="map" resultType="blog">
select * from blog where 1=1
<choose>
<when test="title != null">
and title=#{title}
</when>
<when test="author != null">
and author=#{author}
</when>
<otherwise>
and views=#{title}
</otherwise>
</choose>
</select>
choose同java中的switch一样,一旦遇到符合条件的,就只执行当前的判断,其他的不再执行。还有一点,如果前面没有找到条件,就会执行otherwise,如果你没有传递参数,otherwise也会执行。
trim(where,set)
where和set可以智能的为我们的sql语句添加或者减少一些东西,从而使我们的sql语句变的合法。
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。并且where也可以和choose,if进行嵌套使用。
<select id="getBlogWhere" parameterType="map" resultType="blog">
select * from blog
<where>
<choose>
<when test="title != null">
and title=#{title}
</when>
<when test="author != null">
and author=#{author}
</when>
</choose>
</where>
</select>
<!--这是嵌套if的情况下-->
<select id="getBlogWhere" parameterType="map" resultType="blog">
select * from blog
<where>
<if test="title != null">
and title=#{title}
</if>
<if test="author != null">
and author=#{author}
</if>
</where>
</select>
用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列,比如:
<update id="updateBlogInfo" parameterType="map" >
update blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author},
</if>
</set>
<where>
id = #{id}
</where>
</update>
官方文档也说了,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的),也就是说这个例子中第二个逗号会被删除。如果set元素中的属性都为空,那么就会出错。
foreach
<select id="getBlogForeach" parameterType="map" resultType="blog">
select * from blog
<where>
<if test="author != null">
and author = #{author}
</if>
<foreach collection="ids" open="(" close=")" separator="or" item="idItem">
id = #{idItem}
</foreach>
</where>
</select>
collection集合是你map集合中的list名,open和close分别表示以什么开始和以什么结束,separator表示分隔符,idItem是传递给下面的名字。
sql片段
使用sql标签抽取公共的部分,在需要使用的地方使用include标签即可。
注意:
- 最好在一张表中定义sql片段。
- 不要有where标签
最后,动态sql就是在拼接sql语句,只要保证sql的正确性,按照sql的格式去排列组合就行了。
缓存
什么是缓存?
- 存在内存中的临时数据。
- 将用户经常查询的数据放在内存中,用户再次查询时就不用从磁盘中查询了,从而提高查询效率,解决高并发系统的性能问题。
为什么使用缓存?
减少和数据库的操作次数,减少系统开销,提高系统效率。
一级缓存
默认情况下,mybatis的一级缓存是开启的,二级缓存需要我们手动开启。
一级缓存就是我们开启一个SqlSession到关闭一个SqlSession的过程。
测试:
可以看到blog1与blog2是相同的。
缓存失效的情况:
- 1:查询的东西不同
- 2:增删改操作,就是可能会改变数据库数据的操作,缓存会失效
- 3:查询不同的mapper.xml缓存会失效这是必定的
- 4:手动清理缓存 — > sqlSession.clearCache();
二级缓存
由于一级缓存作用域太低,所以二级缓存就产生了,是基于namespace级别的缓存,一个名称空间对应于一个缓存。
工作机制:
- 一个会话查询一条数据,这个数据就会被保存在当前会话的一级缓存中
- 如果当前会话关闭了,这和会话对应的一级缓存就没了
- 如果想要继续读取数据,就要将数据保存在二级缓存中
- 不同的mapper查出的数据会放在自己对应的缓存中
使用步骤:
1:显示开启缓存
<setting name="cacheEnabled" value="true"/>
2:在mapper.xml中进行使用
在select和insert等标签中我们也可以定义不开启缓存
注意:有时候我们的程序可能会出错,我们可以通过报错进行检查,最可能的就是实体类没有序列化。
总结:
- 只要开启了二级缓存,在一个mapper下就有效
- 所有的数据都会先放在一级缓存中
- 只要当会话提交或者关闭的时候,才会提交到二级缓存
缓存原理:
当用户查询数据时首先访问二级缓存,如果二级缓存有,则返回查询的信息,如果二级缓存没有,则访问一级缓存,如果一级缓存有,则返回,如果一级缓存没有则去查询数据库。然后保存在存储在缓存中。
(图片来自mybatis狂神说)