Mybatis篇
1、Mybatis是什么
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。
详解:
MyBatis 是支持普通 SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis 消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。MyBatis 使用简单的 XML或注解用于配置和原始映射,将接口和 Java 的POJOs(Plain Ordinary Java Objects,普通的 Java对象)映射成数据库中的记录。
每个MyBatis应用程序主要都是使用SqlSessionFactory实例的,一个SqlSessionFactory实例可以通过SqlSessionFactoryBuilder获得。SqlSessionFactoryBuilder可以从一个xml配置文件或者一个预定义的配置类的实例获得。
用xml文件构建SqlSessionFactory实例是非常简单的事情。推荐在这个配置中使用类路径资源(classpath resource),但你可以使用任何Reader实例,包括用文件路径或file://开头的url创建的实例。MyBatis有一个实用类----Resources,它有很多方法,可以方便地从类路径及其它位置加载资源。
2、Mybatis的优点
- 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件。易于学习,易于使用。通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
- 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
- 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
- 提供映射标签,支持对象与数据库的orm字段关系映射。
- 提供对象关系映射标签,支持对象关系组建维护。
- 提供xml标签,支持编写动态sql。 [2]
3、Mybatis的运作流程
为了使大家能够更加清晰的理解MyBatis程序,在正式讲解MyBatis入门案例之前,先来了解一下MyBatis程序的工作原理,如图1所示。
从图可以看出,MyBatis框架在操作数据库时,大体经过了8个步骤。下面就对图1中的每一步流程进行详细讲解,具体如下。
(1)读取MyBatis配置文件mybatis-config.xml。mybatis-config.xml作为MyBatis的全局配置文件,配置了MyBatis的运行环境等信息,其中主要内容是获取数据库连接。
(2)加载映射文件Mapper.xml。Mapper.xml文件即SQL映射文件,该文件中配置了操作数据库的SQL语句,需要在mybatis-config.xml中加载才能执行。mybatis-config.xml可以加载多个配置文件,每个配置文件对应数据库中的一张表。
(3)构建会话工厂。通过MyBatis的环境等配置信息构建会话工厂SqlSessionFactory。
(4)创建SqlSession对象。由会话工厂创建SqlSession对象,该对象中包含了执行SQL的所有方法。
(5)MyBatis底层定义了一个Executor接口来操作数据库,它会根据SqlSession传递的参数动态的生成需要执行的SQL语句,同时负责查询缓存的维护。
(6)在Executor接口的执行方法中,包含一个MappedStatement类型的参数,该参数是对映射信息的封装,用来存储要映射的SQL语句的id、参数等。Mapper.xml文件中一个SQL对应一个MappedStatement对象,SQL的id即是MappedStatement的id。
(7)输入参数映射。在执行方法时,MappedStatement对象会对用户执行SQL语句的输入参数进行定义(可以定义为Map、List类型、基本类型和POJO类型),Executor执行器会通过MappedStatement对象在执行SQL前,将输入的Java对象映射到SQL语句中。这里对输入参数的映射过程就类似于JDBC编程中对preparedStatement对象设置参数的过程。
(8)输出结果映射。在数据库中执行完SQL语句后,MappedStatement对象会对SQL执行输出的结果进行定义(可以定义为Map和List类型、基本类型、POJO类型),Executor执行器会通过MappedStatement对象在执行SQL语句后,将输出结果映射至Java对象中。这种将输出结果映射到Java对象的过程就类似于JDBC编程中对结果的解析处理过程。
通过上面对MyBatis框架执行流程的讲解,相信读者对MyBatis框架已经有了一个初步的了解。对于初学者来说,上面所讲解的内容可能不会完全理解,现阶段也不要求读者能完全理解,这里讲解MyBatis框架的执行过程是为了方便后面程序的学习。在学习完MyBatis框架后,读者自然就会明白上面所讲解的内容了。
4、为什么要学习Mybatis
通过上边对mybatis的介绍,mybatis可以做什么,想必心里有点底了,mybatis主要用于j2ee企业级应用开发的持久层,用于处理java程序与数据库之间的交互。
在j2ee企业级应用开发中,其实并不只有mybatis,还有hibernate、ebean、jpa等其他持久层解决方案可以选择,那么为什么要选择使用mybatis呢?其实这个问题答案并不固定,因为,你也可以不用mybatis呀,这里就要多说一句了,互联网技术里边,除了真正存在bug的,其余的每一个技术都有存在的价值,毕竟存在即合理,之前不是有人说:"不选贵的,只选对的",其实在技术选择上也是这个道理,适合的才是最好的,在真正应用开发时,并不是用最新的技术就是最好的,要根据自己将要开发的应用的特点、成本等多个方面进行考量到底使用什么技术。
言归正传,那么哪种项目更加适合选择使用mybatis呢?这里拿hibernate来比较一下,因为纯介绍mybatis的有特点可能感觉不出什么,hibernate和mybatis是使用最多的两个持久层框架,比较一下选择时可能会更清晰,接下来将在对ORM的支持、灵活性、开发效率、适用场景四个方面进行阐述。
hibernate是全ORM框架,mybatis是半ORM框架,需要自己写sql,其实这就是这个问题的根源,正因为特性不一样,因此就自然而然的导致了其他地方不一样。因为hibernate是全ORM框架,因此在映射规则确定的情况下只需要操作对应的java数据模型即可,因此用起来开发效率是高的(因为不需要自动维护一堆sql呀,只需要直接调用内置的方法即可),也正因为这样,映射规则的确定是复杂的,前期对象之间关系固定后,后期是不容易改变的,而转言mybatis,因为是半ORM框架,sql是自己写的,对应的映射规则也是相应维护的,因此后期如果数据模型和之间的关系改变了,mybatis改变起来更加简单;也正因为上述的,mybatis也更加灵活,正因为sql可以自己开发,因此在优化sql上更容易实现,而hibernate就不见得了;不过在开发效率上,在适合自己的领域,hibernate框架肯定是开发效率更胜一筹。
通过上边说到的,其实最本质的核心也就一个,mybatis可以自己写sql,因此灵活性更高,因此在开发一些业务经常调整的项目时可以使用,例如:电商,而hibernate则更加适合于一些业务模式固化的项目,例如:OA。
5、什么是动态sql
一、简单来说就是在我们的xml文件中根据需求动态的拼接sql语句使用步驟
1.新建数据表
CREATE TABLE `blog` (
`id` varchar(50) NOT NULL COMMENT '牛客id',
`title` varchar(100) NOT NULL COMMENT '牛客标题',
`author` varchar(30) NOT NULL COMMENT '牛客作者',
`create_time` datetime NOT NULL COMMENT '创建时间',
`views` int(30) NOT NULL COMMENT '浏览量'
) ENGINE=InnoDB DEFAULT CHARSET=utf8
2.IDutil工具类
代码如下(示例):
public class IDUtil {
public static String genId(){
return UUID.randomUUID().toString().replaceAll("-","");
}
}
3 实体类
<a href="/profile/1954537" data-card-uid="1954537" class="" target="_blank" from-niu="default" data-card-index="3">@Data public class Blog {
private String id;
private String title;
private String author;
private Date createTime;
private int views;
}</a>
4、编写Mapper接口及xml文件
public interface BlogMapper {
}
<?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="com.kuang.dao.BlogMapper">
</mapper>
5、mybatis核心配置文件,下划线驼峰自动转换
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--注册Mapper.xml-->
<mappers>
<mapper class="com.kuang.dao.BlogMapper"/>
</mappers>
插入初始数据
6、接口
//新增一个blog int addBlog(Blog blog);
7、sql配置文件
<insert id="addBlog" parameterType="blog">
insert into blog (id, title, author, create_time, views)
values (#{id},#{title},#{author},#{createTime},#{views});
</insert>
8、测试
@Test
public void addInitBlog(){
SqlSession session = MybatisUtils.getSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = new Blog();
blog.setId(IDUtil.genId());
blog.setTitle("Mybatis");
blog.setAuthor("小马哥");
blog.setCreateTime(new Date());
blog.setViews(9999);
mapper.addBlog(blog);
blog.setId(IDUtil.genId());
blog.setTitle("码出宇宙");
mapper.addBlog(blog);
blog.setId(IDUtil.genId());
blog.setTitle("菜鸟刷题");
mapper.addBlog(blog);
blog.setId(IDUtil.genId());
blog.setTitle("牛客刷题");
mapper.addBlog(blog);
session.close();
}
f语句
需求:根据作者名字和博客名字来查询博客!如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询
1、接口
//需求1 List<Blog> queryBlogIf(Map map);
2、编写sql
<!--需求1:
根据作者名字和博客名字来查询博客!
如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询
select * from blog where title = #{title} and author = #{author}
-->
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from blog where
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</select>
3、测试
@Test
public void testQueryBlogIf(){
SqlSession session = MybatisUtils.getSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, String> map = new HashMap<String, String>();
map.put("title","Mybatis如此简单");
map.put("author","小马哥");
List<Blog> blogs = mapper.queryBlogIf(map);
System.out.println(blogs);
session.close();
}
这样写我们可以看到,如果 author 等于 null,那么查询语句为 select * from user where title=#{title},但是如果title为空呢?那么查询语句为 select * from user where and author=#{author},这是错误的 SQL 语句,如何解决呢?请看下面的 where 语句!
4、where
修改上面的SQL语句;
@Test
public void testQueryBlogIf(){
SqlSession session = MybatisUtils.getSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, String> map = new HashMap<String, String>();
map.put("title","Mybatis如此简单");
map.put("author","小马哥");
List<Blog> blogs = mapper.queryBlogIf(map);
System.out.println(blogs);
session.close();
}
这个"where"标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉。
set
同理,上面的对于查询 SQL 语句包含 where 关键字,如果在进行更新操作的时候,含有 set 关键词,我们怎么处理呢?
1、编写接口方法
int updateBlog(Map map);
2、sql配置文件
<!--注意set是用的逗号隔开-->
<update id="updateBlog" parameterType="map">
update blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author}
</if>
</set>
where id = #{id};
</update>
3、测试
@Test
public void testUpdateBlog(){
SqlSession session = MybatisUtils.getSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, String> map = new HashMap<String, String>();
map.put("title","动态SQL");
map.put("author","小马哥");
map.put("id","9d6a763f5e1347cebda43e2a32687a77");
mapper.updateBlog(map);
session.close();
}
choose
有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句
1、编写接口方法
choose 有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句 1、编写接口方法
List<Blog> queryBlogChoose(Map map);
2、sql配置文件
<select id="queryBlogChoose" parameterType="map" resultType="blog">
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>
3、测试类
@Test
public void testQueryBlogChoose(){
SqlSession session = MybatisUtils.getSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("title","Mybatis");
map.put("author","小马哥");
map.put("views",9999);
List<Blog> blogs = mapper.queryBlogChoose(map);
System.out.println(blogs);
session.close();
}
SQL片段
有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。
SQL片段 有时候可能某个 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、引用sql片段
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from blog
<where>
<!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace -->
<include refid="if-title-author"></include>
<!-- 在这里还可以引用其他的 sql 片段 -->
</where>
</select>
注意:
①、最好基于 单表来定义 sql 片段,提高片段的可重用性
②、在 sql 片段中不要包括 where
Foreach
将数据库中前三个数据的id修改为1,2,3;
需求:我们需要查询 blog 表中 id 分别为1,2,3的博客信息
1、编写接口
List<Blog> queryBlogForeach(Map map);
2、编写sql语句
<select id="queryBlogForeach" parameterType="map" resultType="blog">
select * from blog
<where>
<!--
collection:指定输入对象中的集合属性
item:每次遍历生成的对象
open:开始遍历时的拼接字符串
close:结束时拼接的字符串
separator:遍历对象之间需要拼接的字符串
select * from blog where 1=1 and (id=1&nbs***bsp;id=2&nbs***bsp;id=3)
-->
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id=#{id}
</foreach>
</where>
</select>
3、测试
@Test
public void testQueryBlogForeach(){
SqlSession session = MybatisUtils.getSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap map = new HashMap();
List<Integer> ids = new ArrayList<Integer>();
ids.add(1);
ids.add(2);
ids.add(3);
map.put("ids",ids);
List<Blog> blogs = mapper.queryBlogForeach(map);
System.out.println(blogs);
session.close();
}
小结
其实动态 sql 语句的编写往往就是一个拼接的问题,为了保证拼接准确,我们最好首先要写原生的 sql 语句出来,然后在通过 mybatis 动态sql 对照着改,防止出错。多在实践中使用才是熟练掌握它的技巧。
6、Mybatis框架中xml文件中常用的标签
除了常见的<select>、<insert>、<update>、 <delete>标签以外,
还有<resultMap>、<parameterMap>(以被弃用)、<sql>、 <include>、 <selectKey>,
加上动态sql的9个标签,trim、where、set、foreach、if、choose、when、otherwise、bind等,
其中<sql>为sql片段标签,通过<include>标签引入sql片段,<selectKey>为不支持自增的主键生成策略标签。
一、常见标签
1、select
<select id="queryUserById" resultType="User">
select id,username,password,gender,regist_time
from t_user
where id = #{arg0}
</select>
<select id="queryUserByIdAndUsername" resultType="User">
select *from t_user where id = #{id} and username = #{username}
</select>
2、insert和selectKey
<insert id="insertUser" parameterType="User">
insert into t_user(username,password,gender,birth)
values (username=#{username},password=#{password},gender=#{gender},birth=#{birth})
</insert>
<insert id="insertUser" parameterType="User">
<selectKey order="AFTER" resultType="int" keyProperty="id">
select last_insert_id()
</selectKey>
insert into t_user(username,password,gender,birth)
values (username=#{username},password=#{password},gender=#{gender},birth=#{birth})
</insert>
<insert id="insertUser" parameterType="User">
<selectKey order="BEFORE" resultType="String" keyProperty="id">
select replace(UUID(),"_","")
</selectKey>
insert into t_user(username,password,gender,birth)
values (username=#{username},password=#{password},gender=#{gender},birth=#{birth})
</insert>
3、update
<update id="updateUser" parameterType="User">
update t_user
set username=#{username},password=#{password},gender=#{gender},birth=#{birth}
where id = #{id}
</update>
4、delete
<delete id="deleteById" parameterType="int">
delet from t_user
where id = #{id}
</delete>
5、resultMap
<resultMap id="rm" type="User">
<id column="id" property="id"></id>
<result column="password" property="password"></result>
<result column="username" property="username"></result>
<result column="gender" property="gender"></result>
<!--描述自定义属性类passengers:id nationality expire 和passport的映射规则-->
<association property="address" javaType="Address">
<id colum="aid" property="aid"></id>
<result column="address_info" property="address_info"></result>
</association>
<!--描述集合的映射-->
<collection property="order" ofType="Order">
<id colum="oid" property="oid"></id>
<result column="product" property="product"></result>
</collection>
</resultMap>
<select id="queryUser" resultMap="tm">
select u.id,u.username,u.password,u.gender,a.aid,a.address_info,o.oid,o.product
from t_user u join t_address a
on u.id = a.uid
join t_order o
on u.id = o.uid
where id=#{id}
</select>
6、sql和include
<sql id="hhh">select *from t_user</sql>
<select id="queryUserByid" resultType="User">
<include refid="hhh"/>
where id=#{id}
</select>
二、动态sql的9个标签
1、if
<sql id="hhh">select *from t_user</sql>
<select id="queryUser" resultType="User">
<include refid="hhh"/>
where
<if test="username!=null">
username=#{username}
</if>
<if test="password!=null">
password=#{password}
</if>
</select>
2、where
<sql id="hhh">select *from t_user</sql>
<select id="queryUserByUnameAndPwd resultType="User">
<include refid="hhh"/>
<where>
<if test="username!=null">
username = #{username}
</if>
<if test="password!=null">
and password = #{password}
</if>
</where>
</select>
3、set
<update id="updateUser" parameterType="User">
update t_user
<set>
<if test="password!=null">
password = #{password},
</if>
<if test="username!=null">
username=#{username}
</if>
where id = #{id}
</update>
以上的逗号username因为是最后一个不加逗号
4、trim
<select id="queryUserById2" resultType="User">
<include refid="user_fired"/>
<!--prefix="where" 前缀为where,
prefixOverrides="or|and" where子句中如果以or或者and开头,会被覆盖-->
<trim prefix="where" prefixOverrides="or|and">
<if test="username!=null">
username = #{username}
</if>
<if test="gender!=null">
and gender = #{gender}
</if>
</trim>
</select>
<update id="updateUserById" parameterType="User">
update t_user
<!--prefix="set" 前缀为set,
suffixOverrides=","自动将最后一个逗号删除-->
<trim prefix="set" suffixOverrides=",">
<if test="username!=null">
username = #{username},
</if>
<if test="gender!=null">
gender = #{gender},
</if>
</trim>
where id = #{id}
</update>
5、foreach
<delete id="deleteUsersById" parameterType="java.util.List">
delete from t_user where id in
<foreach collection="list" open="(" close=")" separator="," item="i">
#{i}
</foreach>
</delete>
<insert id="insertUsers" parameterType="java.util.List">
insert into t_user values
<foreach collection="list" open="" close="" separator="," item="u">
(null,#{u.username},#{u.password},#{u.gender},#{u.registTime})
</foreach>
</insert>
6、choose和when和otherwise
<select id="findUser" resultType="User">
select *from t_user where id = #{id}
<choose>
<when test="username!=null">
AND username like #{username}
</when>
<when test="password!=null">
AND password = #{password}
</when>
<otherwise>
AND gender = 1
</otherwise>
</choose>
</select>
7、bind
<?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="com.how2java.pojo">
<!-- 本来的模糊查询方式 -->
<!-- <select id="listProduct" resultType="Product"> -->
<!-- select * from product_ where name like concat('%',#{0},'%') -->
<!-- </select> -->
<select id="listProduct" resultType="Product">
<bind name="likename" value="'%' + name + '%'" />
select * from product_ where name like #{likename}
</select>
</mapper>
8、when
<select id="findUser" resultType="User">
select *from t_user where id = #{id}
<choose>
<when test="username!=null">
AND username like #{username}
</when>
<when test="password!=null">
AND password = #{password}
</when>
<otherwise>
AND gender = 1
</otherwise>
</choose>
</select>
9、otherwise
<select id="findUser" resultType="User">
select *from t_user where id = #{id}
<choose>
<when test="username!=null">
AND username like #{username}
</when>
<when test="password!=null">
AND password = #{password}
</when>
<otherwise>
AND gender = 1
</otherwise>
</choose>
</select>
7、Mybatis中的事务处理:(单)
利用MyBatis框架的配置管理比直接使用JDBC API编写事务控制要来得更加轻松,这里我们就来详解Java的MyBatis框架中的事务处理,尤其是和Spring框架集成后更加exciting
1.MyBatis单独使用时,使用SqlSession来处理事务:
public class MyBatisTxTest {
private static SqlSessionFactory sqlSessionFactory;
private static Reader reader;
@BeforeClass
public static void setUpBeforeClass() throws Exception {
try {
reader = Resources.getResourceAsReader("Configuration.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
} finally {
if (reader != null) {
reader.close();
}
}
}
@Test
public void updateUserTxTest() {
SqlSession session = sqlSessionFactory.openSession(false); // 打开会话,事务开始
try {
IUserMapper mapper = session.getMapper(IUserMapper.class);
User user = new User(9, "Test transaction");
int affectedCount = mapper.updateUser(user); // 因后面的异常而未执行commit语句
User user = new User(10, "Test transaction continuously");
int affectedCount2 = mapper.updateUser(user2); // 因后面的异常而未执行commit语句
int i = 2 / 0; // 触发运行时异常
session.commit(); // 提交会话,即事务提交
} finally {
session.close(); // 关闭会话,释放资源
}
}
}
8、Mybatis的xml配置的顺序
<?xml version="1.0" encoding="UTF-8"?>
<configuration><!--配置-->
<properties/><!--属性-->
<settings/><!--设置-->
<typeAliases/><!--类型别名-->
<typeHandlers/><!--类型处理器-->
<objectFactory/><!--对象工厂-->
<plugins/><!--插件-->
<environments><!--配置环境-->
<environment><!--环境变量-->
<transactionManager/><!--事务管理器-->
<dataSource/><!--数据源-->
</environment>
</environments>
<databaseidProvider/><!--数据库厂商标识-->
<mappers/><!--映射器-->
</configuration>
9、Mybatis核心依赖
<dependencies>
<!--Mybatis核心-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!--junit测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--Mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.3</version>
</dependency>
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
</dependencies>
10、日志工厂
如果一个数据库操作,出现了异常,我们需要排错,日志就是最好的助手
曾经:sout,debug
现在:日志工厂!
- SLF4J
- Apache Commons Logging
- Log4j 2
- Log4j
- JDK logging
- STDOUT_LOGGING
- NO_LOGGING
在MyBatis中具体使用哪一个日志实现,在设置中设定。
STDOUT_LOGGING标准日志输出 在mybatis核心配置文件中配置我们的日志
<settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings>
1、 LOG4J
什么是Log4j?
Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程
我们也可以控制每一条日志的输出格式
通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程
通过一个配置文件来灵活地进行配置,而不需要修改应用的代码
1 导入依赖
2 配置log4j.properties
3 配置log4j为日志的实现
4 Log4j的使用!直接测试运行已有的查询
5 简单使用
11、一对多,多对一
本次例子要完成的是Mybatis关系映射中一对多的关系:比如一个学生对应一个专业,一个专业对应多个学生。
需求:1、根据学号查找该学生以及用查询后学生的专业外键值查出该生专业对象
2、根据专业id值查出专业以及所有属于该专业的学生
mybatis的映射实现方法有两种:1、通过一条sql语句的映射和自定义的resultMap结果集得到一个包含专业对象的学生对象结果或一个包含学生集合对象的专业对象结果集。如:select * from stuinfo s,major m where s.sno=#{sno} and s.majorId=m.id
2、通过两条sql语句的先后执行(其中一条查询的条件为另一个的执行结果)
如:先查询出学生对象select * from stuinfo where sno=#{sno},再通过外键查出专业:select * from major where id=#{majorId}
mybatis 中映射的实现方法:
其中学生表外键majorId是专业表的主键
1、项目目录结构如下
2、Major实体类
3、Stuinfo实体类
4、mybatis.xml
5、majorMapper.xml
6、StuinfoMapper.xml
7、DBtool
8、InterDaoMapper
9、MapperImpl
10、test
11、结果
12、Mybatis动态代理
动态代理实战
众所周知哈,Mybatis 底层封装使用的 JDK 动态代理。说 Mybatis 动态代理之前,先来看一下平常我们写的动态代理 Demo,抛砖引玉
一般来说定义 JDK 动态代理分为三个步骤,如下所示
- 定义代理接口
- 定义代理接口实现类
- 定义动态代理调用处理器
三步代码如下所示,玩过动态代理的小伙伴看过就能明白
写个测试程序,运行一下看看效果,同样是分三步
- 创建被代理接口的实现类
- 创建动态代理类,说一下三个参数
- 类加载器
- 被代理类所实现的接口数组
- 调用处理器(调用被代理类方法,每次都经过它)
- 被代理实现类调用方法
13、Mybatis 代理源码实现
14、抽象类能否 JDK 动态代理
15、什么是缓存
缓存是存在于内存中的临时数据。 使用缓存减少和数据库的交互次数,提高执行效率。
1、适用于缓存 经常查询并且不经常改变的; 数据的正确与否对最终结果影响不大的; 2、不适用于缓存 经常改变的数据; 数据的正确与否对最终结果影响很大的; 例如:商品的库存,银行的汇率,股市的牌价;
16、mybatis一级缓存
1、一级缓存简介 一级缓存作用域是sqlsession级别的,同一个sqlsession中执行相同的sql查询(相同的sql和参数),第一次会去查询数据库并写到缓存中,第二次从一级缓存中取。
一级缓存是基于 PerpetualCache 的 HashMap 本地缓存,默认打开一级缓存。
2、何时清空一级缓存 如果中间sqlSession去执行commit操作(执行插入、更新、删除),则会清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
一级缓存时执行commit,close,增删改等操作,就会清空当前的一级缓存;当对SqlSession执行更新操作(update、delete、insert)后并执行commit时,不仅清空其自身的一级缓存(执行更新操作的效果),也清空二级缓存(执行commit()的效果)。
3、一级缓存无过期时间,只有生命周期 MyBatis在开启一个数据库会话时,会创建一个新的SqlSession对象,SqlSession对象中会有一个Executor对象,Executor对象中持有一个PerpetualCache对象,见下面代码。当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。
17、mybatis二级缓存
1、二级缓存简介 它指的是Mybatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。
二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。
2、二级缓存何时存入 在关闭sqlsession后(close),才会把该sqlsession一级缓存中的数据添加到namespace的二级缓存中。
开启了二级缓存后,还需要将要缓存的pojo实现Serializable接口,为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一定只存在内存中,有可能存在硬盘中。
3、二级缓存有过期时间,但没有后台线程进行检测 需要注意的是,并不是key-value的过期时间,而是这个cache的过期时间,是flushInterval,意味着整个清空缓存cache,所以不需要后台线程去定时检测。
每当存取数据的时候,都有检测一下cache的生命时间,默认是1小时,如果这个cache存活了一个小时,那么将整个清空一下。
4、当 Mybatis 调用 Dao 层查询数据库时,先查询二级缓存,二级缓存中无对应数据,再去查询一级缓存,一级缓存中也没有,最后去数据库查找。
18、{}和${}的区别是什么?
- {}是预编译处理,${}是字符串替换。
- Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
- Mybatis在处理时 , 就 是 把 {}时,就是把时,就是把{}替换成变量的值。
- 使用#{}可以有效的防止SQL注入,提高系统安全性。
19、简述Mybatis的Xml映射文件和Mybatis内部数据结构之间的映射关系?
Mybatis将所有Xml配置信息都封装到All-In-One重量级对象Configuration内部。在Xml映射文件中,<parameterMap>标签会被解析为ParameterMap对象,其每个子元素会被解析为ParameterMapping对象。<resultMap>标签会被解析为ResultMap对象,其每个子元素会被解析为ResultMapping对象。每一个 <select>、<insert>、<update>、<delete>标签均会被解析为MappedStatement对象,标签内的sql会被解析为BoundSql对象。
20、如何执行批量插入?
使用forEach标签或者使用 sqlSessionTemplate
21、通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?
Dao接口即Mapper接口。接口的全限名,就是映射文件中的namespace的值;接口的方法名,就是映射文件中Mapper的Statement的id值;接口方法内的参数,就是传递给sql的参数。 Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MapperStatement。在Mybatis中,每一个 <select>、 <insert>、<update>、<delete>标签,都会被解析为一个MapperStatement对象。 举例:
com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespace为com.mybatis3.mappers.StudentDao下面 id 为findStudentById 的 MapperStatement。 Mapper接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略。Mapper接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象proxy,代理对象会拦截接口方法,转而执行MapperStatement所代表的sql,然后将sql执行结果返回。
22、说说Mybaits的优缺点
优点
① 基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。
② 与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接;
③ 很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)。
④ 能够与Spring很好的集成;
⑤ 提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。
缺点
① SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。
② SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
MyBatis框架适用场合:
(1)MyBatis专注于SQL本身,是一个足够灵活的DAO层解决方案。
(2)对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis将是不错的选择。
23、Mybatis是如何进行分页的?
Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页,先把数据都查出来,然后再做分页。
可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
分页插件的基本原理是什么?
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql(SQL拼接limit),根据dialect方言,添加对应的物理分页语句和物理分页参数,用到了技术JDK动态代理,用到了责任链设计模式。
24、简述Mybatis的插件运行原理?
Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口的插件,Mybatis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。
25、如何编写一个插件?
编写插件:实现Mybatis的Interceptor接口并复写intercept()方法,然后再给插件编写注解,指定要拦截哪一个接口的哪些方法即可,最后在配置文件中配置你编写的插件。推荐:建议收***ybatis插件原理详解
26、Mybatis拦截器介绍
MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能。那么拦截器拦截MyBatis中的哪些内容呢?
我们进入官网看一看:
MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) ParameterHandler (getParameterObject, setParameters) ResultSetHandler (handleResultSets, handleOutputParameters) StatementHandler (prepare, parameterize, batch, update, query) 我们看到了可以拦截Executor接口的部分方法,比如update,query,commit,rollback等方法,还有其他接口的一些方法等。
总体概括为:
- 拦截执行器的方法
- 拦截参数的处理
- 拦截结果集的处理
- 拦截Sql语法构建的处理
27、拦截器的使用
28.JDBC编程有哪些不足之处,Mybatis是如何解决这些问题的?
① 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
解决:在SqlMapConfig.xml中配置数据链接池,使用连接池管理数据库链接。
② Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。
③ 向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。
解决: Mybatis自动将java对象映射至sql语句。
④ 对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
解决:Mybatis自动将sql执行结果映射至java对象。
29.Mybatis与Hibernate有哪些不同?
Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句,不过mybatis可以通过XML或注解方式灵活配置要运行的sql语句,并将java对象和sql语句映射生成最终执行的sql,最后将sql执行的结果再映射生成java对象。
Mybatis学习门槛低,简单易学,程序员直接编写原生态sql,可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大。
Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用hibernate开发可以节省很多代码,提高效率。但是Hibernate的缺点是学习门槛高,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡,以及怎样用好Hibernate需要具有很强的经验和能力才行。
总之,按照用户的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。
30.使用Mybatis的mapper接口调用时有哪些要求?
① Mapper接口方法名和mapper.xml中定义的每个sql的id相同
② Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同
③ Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
④ Mapper.xml文件中的namespace即是mapper接口的类路径。
由于内容较多,就不一一展示全部内容了 !有需要这份《【精选】Mybatis30道面试题》完整学习笔记文档的,麻烦转发后丝信回复[777]即可获取资料免费领取方式!