!!本文主要是博主总结看着玩的,不具有很高的参考价值,慎重
MyBatis是一款优秀的持久层框架,它是一个基于Java语言的ORM框架,通过XML或注解的方式将Java对象和数据库中的表进行映射,实现持久化操作。
mybatis工作原理:
大家可以看一下源码。推荐文章MyBatis底层原理小白版本
mybatis配置文件,包括Mybatis全局配置文件和Mybatis映射文件,其中全局配置文件配置了数据源、事务等信息;映射文件配置了SQL执行相关的信息。
mybatis通过读取配置文件信息(全局配置文件和映射文件),构造出SqlSessionFactory,即会话工厂。
通过SqlSessionFactory,可以创建SqlSession即会话。
Mybatis是通过SqlSession来操作数据库的。SqlSession本身不能直接操作数据库,它是通过底层的Executor执行器接口来操作数据库的。
Executor接口有两个实现类,一个是普通执行器,一个是缓存执行器(默认)。Executor执行器要处理的SQL信息是封装到一个底层对象MappedStatement中。
MyBatis在以下场景下都有着广泛的应用:
1、数据库访问操作:MyBatis提供了灵活的SQL语句编写和自定义的SQL映射,可以更加细粒度地控制SQL语句的执行,因此适用于需要对数据库进行复杂操作的场景。
2、大数据量操作:MyBatis的批量操作和分页查询功能能够很好地处理大量数据的访问和操作,因此适用于需要对大量数据进行操作的场景。
3、高并发访问:MyBatis的缓存机制可以有效地减少数据库访问的次数,提高系统的并发能力,因此适用于需要支持高并发访问的场景。
4、数据库事务处理:MyBatis支持事务处理机制,可以对多个操作进行事务管理,因此适用于需要进行数据库事务处理的场景。
5、多数据源操作:MyBatis可以支持多数据源的操作,可以同时连接多个数据库进行操作,因此适用于需要同时操作多个数据库的场景。
综上所述,MyBatis可以广泛应用于各种需要进行数据库访问和ORM操作的场景,特别是在需要高并发、大数据量和复杂操作的场景下更加适用。
MyBatis具有以下优点:
1、灵活性高:MyBatis提供了丰富的映射语句和灵活的配置,可以满足不同的需求。
2、易于掌握:MyBatis的学习曲线比较平稳,上手比较容易。
3、性能较好:MyBatis通过对SQL语句和结果集的缓存,可以提高系统的性能。
4、可以自由控制SQL:MyBatis支持使用动态SQL来构建复杂的SQL语句,可以自由控制SQL的执行。
5、易于集成:MyBatis可以与Spring等框架进行集成,方便与其他框架一起使用。
总之,MyBatis是一款功能强大、易于使用、性能出色的ORM框架,被广泛应用于各种Java项目中。
依旧建议看底层原理MyBatis底层原理小白版本
MyBatis的工作原理可以简单地概括为以下三个步骤:
1、通过SqlSessionFactoryBuilder创建SqlSessionFactory对象;
2、通过SqlSessionFactory对象的openSession方法创建SqlSession对象;
3、通过SqlSession对象操作数据库,包括执行SQL语句、提交事务等。
MyBatis的工作流程如下:
1、加载MyBatis的配置文件,包括数据库连接信息、映射文件的位置等;
2、解析配置文件,创建Configuration对象,用于保存MyBatis的配置信息;
3、创建SqlSessionFactory(线程安全)对象,是用于创建SqlSession对象的工厂;
4、创建SqlSession对象,用于执行SQL语句的,一个SqlSession对象代表一次数据库连接;
5、通过SqlSession对象执行SQL语句,包括查询、插入、更新、删除等操作;
6、提交事务,如果需要的话;
7、关闭SqlSession对象,释放资源。
接口绑定,就是在MyBatis中任意定义接口,然后把接口里面的方法和SQL语句绑定,我们直接调用接口方法就可以,这样比起原来了SqlSession提供的方法我们可以有更加灵活的选择和设置。
接口绑定有两种实现方式
查询sql的时候给查询出来的列起别名: 我们应尽量给列起别名而不是修改对应类的属性名,后者牵扯的太多
<select id = "getUser" parameterType="int" resultType="user">
select id, password pwd from user where id = #{id}
select>
通过来映射字段名和实体类属性名的一一对应的关系。
<select id = "getUser" parameterType="int" resultMap="userMap">
select * from user where id = #{id}
select>
<resultMap type="user" id="userMap">
<!–用result属性来映射非主键字段,property为实体类属性名,column为数据库表中的属性–>
<result property ="id" column ="id"/>
<result property="pwd" column="password" />
reslutMap>
1. 顺序传参
public User selectUser(String name, int deptId);
<select id="selectUser" resultMap="UserResultMap">
select * from user
where user_name = #{0} and dept_id = #{1}
select>
#{}里面的数字代表传入参数的顺序。
这种方法不建议使用,sql层表达不直观,且一旦顺序调整容易出错。
@Param注解传参法
public User selectUser(@Param("userName") String name, int @Param("deptId") deptId);
<select id="selectUser" resultMap="UserResultMap">
select * from user
where user_name = #{userName} and dept_id = #{deptId}
select>
#{}里面的名称对应的是注解@Param括号里面修饰的名称。
这种方法在参数不多的情况还是比较直观的,推荐使用。
Map传参法
public User selectUser(Map<String, Object> params);
<select id="selectUser" parameterType="java.util.Map" resultMap="UserResultMap">
select * from user
where user_name = #{userName} and dept_id = #{deptId}
select>
#{}里面的名称对应的是Map里面的key名称。
这种方法适合传递多个参数,且参数易变能灵活传递的情况。
Java Bean传参法
public User selectUser(User user);
<select id="selectUser" parameterType="com.jourwon.pojo.User" resultMap="UserResultMap">
select * from user
where user_name = #{userName} and dept_id = #{deptId}
select>
#{}里面的名称对应的是User类里面的成员属性。
这种方法直观,需要建一个实体类,扩展不容易,需要加属性,但代码可读性强,业务逻辑处理方便,推荐使用。
#{}是占位符,预编译处理;${}是拼接符,字符串替换,没有预编译处理。
Mybatis在处理#{}时,#{}传入参数是以字符串传入,会将SQL中的#{}替换为?号,调用PreparedStatement的set方法来赋值。
Mybatis在处理 ${}时,是原值传入,就是把${}替换成变量的值,相当于JDBC中的Statement编译
变量替换后,#{} 对应的变量自动加上单引号 ‘ ’;变量替换后,${} 对应的变量不会加上单引号 ‘ ’
#{} 可以有效的防止SQL注入,提高系统安全性;${} 不能防止SQL 注入
-- 假如 有个 id = 5 or 1=1
delete from 表名 where id = #{id}
delete from 表名 where id = ${id}
-- 执行上面两条sql
第一个会变成这样-> delete from 表名 where id = '5 or 1=1';--执行肯定是失败的
第一个会变成这样-> delete from 表名 where id = 5 or 1=1;--这种情况就是sql注入,如果没有id为5的,那么就会判断 1=1,
这样结果是必然成立的,那么 delete 就会将整个表的数据全都删掉。对于任意一行 where 1=1 都是成立的
#{} 的变量替换是在DBMS 中;${} 的变量替换是在 DBMS 外
怎么理解第六点:
#{}
是mybaits 中的一种动态sql的用法,#{}
用于预编译,由DBMS进行处理。
${}
是一种在很多基于文本的配置文件(比如属性文件、XML配置文件等)中常用的变量替换语法。在这种情况下,
${}
用于将变量的值直接替换到SQL语句或者其他配置中。这种替换发生在应用程序加载配置文件的时候,而不是在DBMS中
'%${question}%'
可能引起SQL注入,不推荐
<select id="getStudent2" parameterType="String" resultMap="StudentTeacher">
select * from student where name like '%${name}%'
select>
"%"#{question}"%"
注意:因为#{…}解析成sql语句时候,会在变量外侧自动加单引号 ’ ',所以这里 % 需要使用双引号" ",不能使用单引号 ’ ’ ,不然会查不到任何结果。
<select id="getStudent2" parameterType="String" resultMap="StudentTeacher">
select * from student where name like "%"#{name}"%"
select>
CONCAT(‘%’,#{question},‘%’) 使用CONCAT()函数,推荐
oracle的concat函数只支持两个参数,但是mysql的concat函数支持好几个,更换数据库就要重新sql
<select id="getStudent2" parameterType="String" resultMap="StudentTeacher">
select * from student where name like concat('%',#{name},'%')
select>
使用bind标签
bind元素的作用是通过OGNL表达式去自定义一个上下文变量,这样更方便使用。在进行模糊查询时,如果是Mysql数据库,常常会用到的是一个concat,它用%和参数相连。然而在Oracle数据库则没有,Oracle数据库使用||进行连接。这样SQL就需要提供两种形式去实现。但是有了bind元素,就不必使用数据库的语言,而是使用MyBatis的动态SQL实现。
<select id="getStudent2" parameterType="String" resultMap="StudentTeacher">
<bind name="pattern" value="'%'+name+'%'"/>
select * from student where name like #{pattern}
select>
1、IF
<select id="findUserById" parameterType="int" resultType="User">
select * from user where 1 = 1
<if test="id != null">
and id = #{id}
if>
<if test="username != null">
and username = #{username}
if>
select>
2、Where元素: Where元素用于将多个条件拼接在一起,如果第一个条件为真,则拼接"WHERE"关键字,否则拼接"AND"关键字,二人且若子句的开头为 ‘and’ 或者 ‘or’ 则也会删掉
<select id="findUser" parameterType="User" resultType="User">
select * from user
<where>
<if test="id != null">
and id = #{id}
if>
<if test="username != null">
and username = #{username}
if>
where>
select>
3、choose (when, otherwise) :Choose元素类似于Java中的switch语句,用于选择一个分支执行。例如:
<select id="findUser" parameterType="User" resultType="User">
select * from user
<where>
<choose>
<when test="id != null">
and id = #{id}
when>
<when test="username != null">
and username = #{username}
when>
<otherwise>
and 1 = 1
otherwise>
choose>
where>
select>
4、Set: Set元素用于更新操作,通过Set元素可以动态地设置更新语句中的字段。例如:
<update id="updateUser" parameterType="User">
update user
<set>
<if test="username != null">
username = #{username},
if>
<if test="password != null">
password = #{password},
if>
set>
where id = #{id}
update>
使用
<select id="queryBlogForeach" parameterType="map" resultType="blog">
select * from blog
<where>
<foreach collection="ids" item="id" open="and (" separator="or" close=")">
id = #{id}
foreach>
where>
select>
第一种,按照结果嵌套查询
这种就简单的理解为 :
我们已经把需要的表并在一起了,我们只需要选择我们需要的属性进行映射就行了。
<select id="getStudent2" resultMap="StudentTeacher2">
select s.id sid,s.name sname, t.name tname
from student s, teacher t
where s.tid = t.id
select>
<resultMap id="StudentTeacher2" type="student">
<result property="id" column="sid"/>
<result property="name" column="name"/>
<association property="teacher" javaType="teacher">
<result property="name" column="tname"/>
association>
resultMap>
第二种:按查询进行嵌套处理
这中就简单的理解为子查询
我们先查学生表的所有信息,将查到的tid 给子查询,让子查询找到对应的数据,再把结果返回
<select id="getStudent" resultMap="StudentTeacher">
select * from student
select>
<resultMap id="StudentTeacher" type="student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<association property="teacher" javaType="teacher" select="getTeacher"column="tid"/>
resultMap>
<select id="getTeacher" resultType="teacher">
select * from teacher where id = #{??};
select>
第一种,按照结果嵌套查询
<select id="getTeacher" resultMap="TeacherStudent">
select s.id sid, s.name sname, t.name tname, t.id tid
from student s,teacher t
where s.tid = t.id and t.id = #{tid}
select>
<resultMap id="TeacherStudent" type="Teacher2">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<collection property="students" javaType="ArrayList" ofType="Student2">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
collection>
resultMap>
第二种:按照查询进行嵌套
<select id="getTeacherById" resultMap="TeacherStudent">
select id ddd, name from teacher where id = #{tid}
select>
<resultMap id="TeacherStudent" type="Teacher2">
<result property="id" column="ddd"/>
<result property="name" column="name"/>
<collection property="students" javaType="ArrayList" ofType="Student2"
select="getStudentByTid" column="ddd"/>
resultMap>
<select id="getStudentByTid" resultType="student2">
select * from student where tid = #{hh}
select>
依旧建议看一下源码
Dao接口,就是人们常说的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中MappedStatement的id值,接口方法内的参数,就是传递给sql的参数。Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MappedStatement,举例:
com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespace为com.mybatis3.mappers.StudentDao下面id = findStudentById的MappedStatement。在Mybatis中,每一个、、、标签,都会被解析为一个MappedStatement对象。
Dao接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略。
Dao接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回。
另一种有争议的答案:
mybatis层面可以实现有条件的重载,但xml配置文件中的id是唯一不可重复的,所以对于重载的多个(同名)方法,都将指向同一个MapperStatement,但有潜在的异常风险,一般不建议使用
public Data getOne()
public Data getOne(@Parme("id") long id)
<select id="getOne" resultType="xxx.xxx.Data">
SELECT * FROM data_table
<if test="id != null">
WHERE id = #{id}
if>
select>
MyBatis的分页方式有五种:基于关键字的分页、基于MyBatis物理分页插件的分页、基于MyBatis逻辑分页插件的分页、基于数据库的分页、基于应用程序的分页。
1、基于limit语句的分页
<select id="getStudent" parameterType="map" resultMap="StudentTeacher">
select * from student limit #{offset},#{count};
select>
2、基于RowBounds:RowBounds是将所有符合条件的数据全都查询到内存中,然后在内存中对数据进行分页,若数据量大,千万别使用RowBounds
mappep.xml里面正常配置,不用对rowBounds任何操作。mybatis的拦截器自动操作rowBounds进行分页。
XML:
<select id="getUserByRowBounds" resultType="student2" >
select * from student
</select>
接口:
List<Student2> getUserByRowBounds(RowBounds rowBounds);
使用:
RowBounds rowBounds = new RowBounds(0,2);
List<Student2> userList = mapper.getUserByRowBounds(rowBounds);
3、基于插件PageHelper(其实自定义插件也行)
导入相应依赖(注意版本匹配问题)
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelperartifactId>
<version>5.2.0version>
dependency>
配置文件(注意依赖的版本)
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
plugin>
plugins>
XML:
<select id="getUserByPageHelper" resultType="student2" >
select * from student
</select>
底层:
List<Student2> getUserByPageHelper();
使用:
int pageNo = 1;//第一页
int pageSize = 2;//每页两条数据
PageHelper.startPage(pageNo,pageSize);//注意,只会匹配最近的一条sql语句,每次使用,都要重新声明
List<Student2> userList = mapper.getUserByPageHelper();
MyBatis的插件机制是一种扩展机制,可以在MyBatis执行SQL语句的各个阶段中插入自定义的逻辑,用于扩展或修改MyBatis的功能。
MyBatis的插件机制主要包括以下几个接口:
Interceptor:Interceptor是MyBatis插件的核心接口,所有的插件都要实现该接口,Interceptor中定义了四个方法,分别是intercept、plugin、setProperties和getProperties。
Executor:Executor是MyBatis的执行器,用于执行SQL语句。
MyBatis提供了三种执行器:SimpleExecutor、ReuseExecutor和BatchExecutor,Executor中定义了一些方法,如query、update等,可以被Interceptor拦截。
ParameterHandler:ParameterHandler用于处理JDBC ParameterStatement的参数,同样可以被Interceptor拦截。
ResultSetHandler:ResultSetHandler用于处理JDBC ResultSet的接口,同样可以被Interceptor拦截。
StatementHandler:StatementHandler是用于处理JDBC Statement的接口,同样可以被Interceptor拦截。
编写MyBatis插件的步骤如下:
1、实现Interceptor接口,并实现intercept方法,该方法中可以插入自定义的逻辑。
2、实现plugin方法,该方法用于返回一个代理对象,该代理对象包含了原始对象和Interceptor对象的逻辑,用于拦截原始对象的方法调用。
3、在mybatis-config.xml中配置插件,将Interceptor的实现类添加到plugins节点中。
下面是一个简单的插件实现示例:
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class MyPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 在执行update方法前,输出日志
System.out.println("Before executing update method.");
Object result = invocation.proceed();
// 在执行update方法后,输出日志
System.out.println("After executing update method.");
return result;
}
@Override
public Object plugin(Object target) {
// 生成代理对象
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 设置插件属性
}
}
在上面的示例中,我们定义了一个MyPlugin类,实现了Interceptor接口,重写了intercept、plugin和setProperties方法。
注解@Intercepts用于标记拦截的对象和方法,@Signature用于标记拦截的方法参数类型。
intercept方法中,我们可以插入自定义的逻辑,例如在执行update方法前后输出日志。
plugin方法中,我们生成了一个代理对象,并将该代理对象返回。
setProperties方法中,我们可以设置插件的属性。
在完成插件的编写后,我们需要在mybatis-config.xml文件中配置插件,
例如:
<plugins>
<plugin interceptor="com.example.MyPlugin">
<property name="property1" value="value1"/>
<property name="property2" value="value2"/>
plugin>
plugins>
在上面的配置中,我们将MyPlugin类作为插件的拦截器,并通过标签设置了插件的属性。
总之,MyBatis的插件机制是一种非常灵活的扩展机制,可以通过自定义的插件实现一些自己的功能,例如日志记录、缓存、分页等。
MyBatis中的延迟加载可以通过两种方式来实现:基于动态代理的延迟加载和基于MyBatis自带的延迟加载机制。
基于动态代理的延迟加载
基于动态代理的延迟加载是MyBatis默认的延迟加载方式。
当查询主对象时,MyBatis只会查询主对象,而不会查询关联对象。
当需要使用关联对象时,MyBatis会通过动态代理的方式生成代理对象,再通过代理对象加载关联对象。
需要使用延迟加载功能时,只需要在Mapper.xml文件中使用或标签指定关联对象即可。
例如:
<association property="author" column="author_id" select="getAuthorById" fetchType="lazy"/>
<collection property="posts" column="id" select="getPostsByBlogId" fetchType="lazy"/>
基于MyBatis自带的延迟加载机制
基于MyBatis自带的延迟加载机制是指使用MyBatis提供的专门负责延迟加载的组件进行延迟加载。
该组件需要在MyBatis的配置文件中进行配置。
使用该组件时,需要在查询语句中使用ResultMap来指定关联对象,以便在查询时进行关联对象的加载。
例如:
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
settings>
<resultMap id="blogResult" type="Blog">
<id property="id" column="id"/>
<result property="title" column="title"/>
<association property="author" column="author_id"
select="getAuthorById" fetchType="lazy"/>
resultMap>
<resultMap id="blogResult" type="Blog">
<id property="id" column="id"/>
<result property="title" column="title"/>
<collection property="posts" column="id"
select="getPostsByBlogId" fetchType="lazy"/>
resultMap>
需要注意的是,基于动态代理的延迟加载方式只支持单个对象的延迟加载,即只能延迟加载一对一关系的关联对象;
而基于MyBatis自带的延迟加载机制则支持一对多关系的关联对象的延迟加载。因此,在使用延迟加载时需要根据具体的场景进行选择。
Mybatis有三种基本的Executor执行器,SimpleExecutor、ReuseExecutor、BatchExecutor。
SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map
BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。
MyBatis的缓存机制是指在执行SQL语句时,将查询结果缓存起来,下次执行相同的SQL语句时,直接从缓存中获取结果,从而提高查询效率。MyBatis的缓存机制分为一级缓存和二级缓存两种。查询顺序为:先查二级缓存,再查一级缓存,最后去数据库
1、一级缓存
一级缓存是指MyBatis的SqlSession级别的缓存,即在同一个SqlSession中执行相同的SQL语句,第二次查询时会直接从缓存中获取结果。
一级缓存的默认开启是通过SqlSessionFactory的默认值配置的,可以通过在mybatis-config.xml文件中的标签中配置开启或关闭,
一级缓存的缓存范围为SqlSession级别,即同一个SqlSession中的所有操作都共享同一个缓存。
一级缓存的缓存失效情况包括以下几种:
(1) SqlSession关闭;
(2) 执行了增删改操作;
(3) 执行了清空缓存操作;
(4) 在SqlSession中显式地调用了clearCache方法。
2、二级缓存
所有的数据都会先放在一级缓存中,只有当会话提交或关闭的时候,才会提交到二级缓存
二级缓存是指MyBatis的Mapper级别的缓存,即在多个SqlSession中执行相同的SQL语句,第二次查询时会直接从缓存中获取结果。
二级缓存的默认开启是通过Mapper的缓存配置实现的,可以通过在Mapper.xml文件中的标签中配置开启或关闭,
例如:
<setting name="cacheEnabled" value="true"/>
二级缓存的缓存范围为Mapper级别,即同一个Mapper的所有SqlSession共享同一个缓存。
二级缓存的缓存失效情况包括以下几种:
(1) 执行了增删改操作;
(2) 执行了清空缓存操作;
(3) 在Mapper.xml文件中的标签中配置了flushInterval属性,表示缓存刷新的时间间隔;
(4) 在Mapper.xml文件中的标签中配置了size属性,表示缓存的最大容量,当缓存的数量超过最大容量时,会触发缓存的清空操作;
(5) 在SqlSession中显式地调用了clearCache方法。