Mybatis
什么是Mybatis?
- MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录
持久层
数据持久化
- 实现程序的数据持久状态和瞬时状态之间的转换过程
- 内存
- 数据库
持久层
- Dao层:完成持久化的操作,通过JDBC与数据库交互
Mybatis程序
- db.properties
//数据库连接的必要配置
driver = com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/xjgl?useSSL=true&useUnicode=true&characterEncoding=utf-8
username = root
password = 123456
- 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"/>
<!--可以配置多个环境但只可以选择使用一个环境-->
<environments default="development">
<environment id="development">
<!--事务管理器:JDBC|MANAGED-->
<transactionManager type="JDBC"/>
<!--数据源:POOLED|UNPOOLED|JNDI-->
<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>
<!--映射器:用于mapper注册:指定对应的mapper.xml文件(相对文件路径:resource|接口类路径:class|全部的接口类:name)-->
<mappers>
<mapper resource="xyz/mintop/dao/ScoreMapper.xml"/>
</mappers>
</configuration>
- MybatisUtil
public class MybatisUtil {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
//使用流获得mybatis-config.xml的配置信息
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//得到配置的SqlSessionFactoryBuilder()用于得到sqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//获得SqlSession对象
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
- pojo类
//从数据库拿到的数据封装在实体类中(通过mapper.xml中的resultType|resultMap)
//注意:实体类需要进行序列化
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Course {
private String cid;
private String cName;
private String tid;
private String classroom;
private String course;
}
- mapper接口类
//执行sql的方法
//与mapper.xml文件相关联,通过方法名和id一致
public interface ScoreMapper {
List<Score> selectScore();//查询方法
//@Param注解的使用,用于少量的参数
List<Score> selectScoreById(@Param("id")String id);//条件查询方法
void insertScore(Score score);//插入方法
//使用Map传递参数,用于多个参数
int upDataScore(Map<String,Object> map); //修改方法
}
- 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">
<mapper namespace="xyz.mintop.dao.ScoreMapper">
<!--id与接口方法名一致-->
<!--resultType:结果返回的类型(数据库列名和实体类变量名相同时)-->
<!--resultMap:结果返回集合,经过resultMap处理,再封装到实体类(数据库列名和实体类变量名不完全相同时)-->
<select id="selectScore" resultType="xyz.mintop.pojo.Score">
select * from score
</select>
<!--parameterType:参数类型-->
<select id="selectScoreById" parameterType="String" resultType="xyz.mintop.pojo.Score">
select * from score where sid = #{id}
</select>
<insert id="insertScore" parameterType="xyz.mintop.pojo.Score">
insert into score values (#{sid},#{cid},#{score})
</insert>
<update id="upDataScore" parameterType="map">
update score set score = #{scores} where cid = #{cId}
</update>
</mapper>
- 测试类
@Test
public void test(){
//获得sqlSession对象
SqlSession sqlSession = MybatisUtil.getSqlSession();
//通过反射获得接口类对象
ScoreMapper mapper = sqlSession.getMapper(ScoreMapper.class);
//执行sql,返回结果对象
List<Score> scores = mapper.selectScore();
for (Score score : scores) {
System.out.println(score);
}
sqlSession.close();
}
注意点:
- mapper需要在xml中注册
- 可以使用注解sql和xml配置sql相结合
Mybatis配置解析
-
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息
-
configuration(配置)
- 在xml中的配置选项要按照规定的顺序
-
properties(属性)
-
这些属性可以在外部进行配置,并可以进行动态替换
-
<properties resource="org/mybatis/example/config.properties">
-
可以在 SqlSessionFactoryBuilder.build() 方法中传入属性值
-
优先级:方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的则是 properties 元素中指定的属性
-
-
settings(设置)
-
typeAliases(类型别名)
-
类型别名可为 Java 类型设置一个缩写名字,用于 XML 配置,意在降低冗余的全限定类名书写
-
<typeAlias alias="Author" type="domain.blog.Author"/>
-
可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean
-
在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名
-
有注解,则别名为其注解值
-
@Alias("author")
-
存在常见的 Java 类型内建的类型别名(见官方文档)
-
-
typeHandlers(类型处理器)
-
objectFactory(对象工厂)
-
plugins(插件)
-
environments(环境配置)
-
environment(环境变量)
- 尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境
-
transactionManager(事务管理器)
- 有两种:JDBC|MANAGED
-
dataSource(数据源)
- 内建:UNPOOLED|POOLED|JNDI
- 第三方:c3p0|dhcp|
- POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间
- JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用
- UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接
-
databaseIdProvider(数据库厂商标识)
-
mappers(映射器)
-
<!-- 使用相对于 类路径 的资源引用 --> <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
-
<!-- 使用映射器 接口实现类 的完全限定类名 --> <mapper class="org.mybatis.builder.AuthorMapper"/>
-
<!-- 将包内的映射器 接口 实现全部注册为映射器 --> <package name="org.mybatis.builder"/>
-
resultMap
-
resultMap 元素是 MyBatis 中最重要最强大的元素
-
<!--结果集映射--> <!--type为对应的实体类名,id为sql标签中resultType映射--> <resultMap id="userResultMap" type="User"> <!--column为数据库中的列名,property为实体类的属性--> <id property="id" column="user_id" /> <result property="username" column="user_name"/> <result property="password" column="hashed_password"/> </resultMap>
日志工厂
logImpl
- STDOUT_LOGGING
指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
<!--标准的日志工厂实现-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
- LOG4J
通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;
可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程;
需要导入依赖的jar包
-
LOG4J使用的配置文件
# priority :debug<info<warn<error #you cannot specify every priority with different file for log4j log4j.rootLogger=debug,stdout,info,warn,error,fileAppender ### 配置输出到文件 ### log4j.appender.fileAppender = org.apache.log4j.FileAppender log4j.appender.fileAppender.File = logs/log.log log4j.appender.fileAppender.Append = true log4j.appender.fileAppender.Threshold = DEBUG log4j.appender.fileAppender.layout = org.apache.log4j.PatternLayout log4j.appender.fileAppender.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n #%p %l #console log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern= [%d{yyyy-MM-dd HH:mm:ss a}]:%m%n #info log log4j.logger.info=info log4j.appender.info=org.apache.log4j.DailyRollingFileAppender log4j.appender.info.DatePattern='_'yyyy-MM-dd'.log' log4j.appender.info.File=./src/com/hp/log/info.log log4j.appender.info.Append=true log4j.appender.info.Threshold=INFO log4j.appender.info.layout=org.apache.log4j.PatternLayout log4j.appender.info.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n #debug log log4j.logger.debug=debug log4j.appender.debug=org.apache.log4j.DailyRollingFileAppender log4j.appender.debug.DatePattern='_'yyyy-MM-dd'.log' log4j.appender.debug.File=./src/com/hp/log/debug.log log4j.appender.debug.Append=true log4j.appender.debug.Threshold=DEBUG log4j.appender.debug.layout=org.apache.log4j.PatternLayout log4j.appender.debug.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n #warn log log4j.logger.warn=warn log4j.appender.warn=org.apache.log4j.DailyRollingFileAppender log4j.appender.warn.DatePattern='_'yyyy-MM-dd'.log' log4j.appender.warn.File=./src/com/hp/log/warn.log log4j.appender.warn.Append=true log4j.appender.warn.Threshold=WARN log4j.appender.warn.layout=org.apache.log4j.PatternLayout log4j.appender.warn.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n #error log4j.logger.error=error log4j.appender.error = org.apache.log4j.DailyRollingFileAppender log4j.appender.error.DatePattern='_'yyyy-MM-dd'.log' log4j.appender.error.File = ./src/com/hp/log/error.log log4j.appender.error.Append = true log4j.appender.error.Threshold = ERROR log4j.appender.error.layout = org.apache.log4j.PatternLayout log4j.appender.error.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
复杂查询
多对一查询
-
子查询
-
<select id="getCourse" resultMap="CourseMap"> select * from course </select> <resultMap id="CourseMap" type="Course"> <result column="课号" property="cid"/> <result column="课名" property="cName"/> <result column="任课教师编号" property="tid"/> <result column="教室" property="classroom"/> <result column="先修课" property="course"/> <association property="teacher" column="任课教师编号" javaType="Teacher" select="getTeacher"/> </resultMap> <select id="getTeacher" resultMap="TeacherMap" parameterType="String"> select * from teacher where 工号 = #{tid} </select> <resultMap id="TeacherMap" type="Teacher"> <result column="工号" property="tid"/> <result column="姓名" property="tName"/> <result column="性别" property="gender"/> <result column="生日" property="birthday"/> <result column="职称" property="title"/> <result column="婚否" property="marry"/> <result column="工资" property="paying"/> </resultMap>
- association:关联
- result:将数据库的列名对应一个实体类的变量
- property:对应实体类中的变量,查询到的结果返回到指定的变量中
- column:上一条查询得到的结果中的指定列,用于下一条查询的参数(类似于子查询)
- javaType:java实体类的类型 ,指定返回的实体类型
- select:下一条要执行的查询
-
联表查询
-
<select id="getCourse2" resultMap="CourseMap2"> select c.课号,c.课名,t.姓名 from course c join teacher t on c.任课教师编号 = t.工号 </select> <resultMap id="CourseMap2" type="Course"> <result property="cid" column="课号"/> <result property="cName" column="课名" /> <association property="teacher" javaType="Teacher"> <result property="tName" column="姓名"/> </association> </resultMap>
- 将查询到的结果返回到指定的变量中
一对多查询
-
按结果映射
<resultMap id="TeacherMap" type="Teacher"> <result column="工号" property="tid"/> <result column="姓名" property="tName"/> <result column="性别" property="gender"/> <result column="生日" property="birthday"/> <result column="职称" property="title"/> <result column="婚否" property="marry"/> <result column="工资" property="paying"/> <collection property="courses" javaType="ArrayList" ofType="Course"> <result column="课号" property="cid"/> <result column="课名" property="cName"/> <result column="任课教师编号" property="tid"/> <result column="教室" property="classroom"/> <result column="先修课" property="course"/> </collection> </resultMap> <select id="getTeacher" resultMap="TeacherMap"> select c.*,t.工号,t.姓名 from teacher t join course c on t.工号 = c.任课教师编号 where t.工号 = #{tid} </select>
- ofType:查询返回的结果对象的类型
-
按查询嵌套处理
<select id="getTeacher2" resultMap="TeacherMap2"> select * from teacher where 工号 = #{tid} </select> <resultMap id="TeacherMap2" type="Teacher"> <result column="工号" property="tid"/> <result column="姓名" property="tName"/> <result column="性别" property="gender"/> <result column="生日" property="birthday"/> <result column="职称" property="title"/> <result column="婚否" property="marry"/> <result column="工资" property="paying"/> <collection property="courses" javaType="ArrayList" ofType="Course" column="工号" select="getCourses"/> </resultMap> <select id="getCourses" resultMap="CourseMap"> select * from course where 任课教师编号 = #{tid} </select> <resultMap id="CourseMap" type="Course"> <result column="课号" property="cid"/> <result column="课名" property="cName"/> <result column="任课教师编号" property="tid"/> <result column="教室" property="classroom"/> <result column="先修课" property="course"/> </resultMap>
动态SQL
IF
-
可以传入多个参数,选择多个条件
-
where 1=1 不安全
<select id="getCourseByIF" resultMap="CourseMap">
select *
from course
<where>
<if test="tid != null">
and 任课教师编号 = #{tid}
</if>
<if test="cName != null">
and 课名 = #{cName}
</if>
</where>
</select>
choose(when,otherwise)
- 只允许传入一个参数,选择一个条件
- 前面语句成立时优先
- 类似于switch
<select id="getCourseByWhen" resultMap="CourseMap">
select *
from course
<where>
<choose>
<when test="tid != null">
and 任课教师编号 = #{tid}
</when>
<when test="cName != null">
and 课名 = #{cName}
</when>
<otherwise>
and 课名 = #{cName}
</otherwise>
</choose>
</where>
</select>
trim(where,set)
-
可以通过自定义 trim 元素来定制 where 元素或set元素的功能。
-
会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。
-
<!--prefix前缀--> <trim prefix="WHERE" prefixOverrides="AND |OR "> ... </trim> <!--suffix后缀--> <trim prefix="SET" suffixOverrides=","> ... </trim>
-
set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号
-
<update id="upDateByTrim" parameterType="map"> update course <set> <if test="cName != null"> 课名 = #{cName}, </if> <if test="tid != null"> 任课教师编号 = #{tid}, </if> <if test="classroom != null"> 教室 = #{classroom}, </if> <if test="course != null"> 先修课 = #{course}, </if> </set> where 课号 = #{cid} </update>
Foreach
- 它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符
<select id="getCourseByForeach" resultMap="CourseMap" parameterType="map">
select *
from course
<where>
<foreach collection="cids" item="cid" open="课号 in (" close=")" separator=",">
#{cid}
</foreach>
</where>
</select>
SQL片段
- 原理:对sql语句进行封装
- 提高代码的重用,提高效率
- where语句不加入sql片段
<!--定义sql片段-->
<sql id="if-tid-cName">
sql语句···
</sql>
<!--调用sql片段-->
<include refid="if-tid-cName"></include>
缓存
什么是缓存
- 存在内存中的数据
- 将一次查询数据放在缓存中,之后再查询相同的数据,直接在缓存中取,不在连接数据库去查询,减少了与数据库的交互,提到效率,解决了高并发系统的性能问题
一级缓存
- 默认情况下,只启用了本地的会话缓存
- 仅仅对一个会话中的数据进行缓存
- 同一个sqlSession对象的缓存
-
缓存失效的原因:
- 不是同一个sqlSession对象
- 数据被修改
- 手动清理缓存
sqlSession.clearCache(); //清理缓存
二级缓存
-
启用全局的二级缓存,只需要在你的 SQL 映射文件中添加 一行
-
可以自定义参数配置二级缓存
-
缓存只作用于 cache 标签所在的映射文件中的语句。
-
二级缓存是基于namespace级别的缓存
-
在xml配置文件中,开启全局缓存,默认为true
<setting name="cacheEnabled" value="true"/>
- 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新,即当sqlSession关闭后,二级缓存取得一级缓存的数据
数据查询过程
- 查看二级缓存
- 查看一级缓存
- 查找数据库