代码:https://github.com/Hello-Qiang/Java
1. 概要
MyBatis的前身是iBATIS,是Clinton Begin在2001年发起的一个开源项目,最初侧重于密码软件的开发,最后发展为一款基于Java的持久层框架。
MyBatis是一款支持自定义的sql查询、持久过程和高级映射的持久层框架,消除了所有的JDBC代码和参数的手动设置以及结果集的检索,MyBatis可以使用XML或注解进行配置和映射,MyBatis通过将参数映射到配置的SQL最终形成执行的SQL语句,最后将执行SQL的结果映射成java对象返回。
与其他的ORM(对象关系映射)框架不同,MyBatis并没有将Java对象与数据库表关联起来,而是将java方法与SQL语句关联。
与JDBC相比MyBatis简化了相关代码,SQL语句在一行代码中就能执行,MyBatis提供了一个映射引擎,声明式的将SQL语句的执行结果与对象树映射起来。通过一种内建的类XML表达式语言,SQl语句可以被动态生成。
MyBatis支持声明式的数据缓存,当一条语句被标记为"可缓存"后,首次执行它时从数据库中获取的所有数据会存储在高速缓冲存储器中,后面再执行这条语句时会从高速缓冲存储器中读取结果,而不是再次访问数据库。MyBatis提供了默认情况下基于java HashMap的缓存实现,以及用于OSCahe、Ehcase、Hazelcast和Memcached连接的默认连接器,同时还提供了API供其他缓存实现使用。
2. MyBatis核心组件
2.1. SqlSesionFactoryBuilder(构造器)
他会根据配置信息XML文件或者Java代码生成SqlSessionFactory(工厂接口),采用的是Builder模式,创建成功后即失去作用(生命周期)
- 推荐使用XML配置文件的形式生成SqlSessionFactory,只有在特殊需要的时候才会使用Java代码的形式生成工厂接口
2.2. SqlSessionFactory(工厂接口)
- 依靠工厂接口来生成SqlSession(会话),使用的是工厂模式,生命周期等同于MyBatis应用周期
- SqlSessionFactory相当于一个对数据库的连接池,一般只创建一个,以单例模式存在。
- 三个作用:获取Mapper接口、发送SQL给数据库、控制数据库事务
2.3. SqlSession(会话)
- 既可以发送SQL语句执行并返回结果,也可以获取Mapper接口,一般会让其在业务逻辑代码中消失,而使用的是MyBatis提供的SQL Mapper接口编程计数,它能够提高代码的可读性和可维护性。
- mybatis存在两种发送SQL的方式:第一种是用SqlSession直接发送SQL(不推荐使用,是iBatis所留下的方式)第二种是通过SqlSession获取Mapper接口再发送。
2.4. SQL Mapper(映射器)
- 他是MyBatis新设计的组件,由一个Java接口和XML文件(或注解)构成,需要给出对应的SQL和映射规则,他发送SQL去执行,并返回结果。
- XML实现映射器需要接口和XML文件(官方推荐),注解配置只需要一个接口就可以
3. MyBatis基本配置
3.1. 标签
MyBatis的配置方式有多种,其中最简单最基础的是使用XML进行配置.
- <typeAliases>元素下面配置了一个包的别名,通常在确定一个类时候需要使用类的全限定名称,为了方便使用,配置类的包名,这样配置后,在使用类的时候,不需要写包名,直接写类名即可。
- 第一种方式是采用标签
- 第二种方式是采用标签
- <mappers>元素中配置了一个包含完整类路径的xml文件,此文件是SQL语句和映射配置文件
- :配置事务管理器(代码中采用的是JDBC的方式)
- :配置数据库,属性type="POOLED"代表采用MyBatis内部提供的连接池方式
3.2. 自动映射和驼峰映射
MyBatis提供了自动映射功能,在默认的情况下自动映射功能是开启的,使用它的好处在于减少大量的映射配置,从而减少工作量。
在Setting元素中有两个可以配置的选项,outoMappingBehavior和mapUnderscoreToCameCase他们是控制自动映射和驼峰的开关,一般而言,自动映射会使用的多一些,因为通过SQL别名机制处理一些细节比较灵活。
3.2.1. 关于自动映射
- 配置自动映射的autoMappingBehavior选项的取值范围是:
- NONE:不进行自动映射
- PARTIAL:默认值,只对没有嵌套结果集进行自动映射
- FULL:对所有的结果集进行自动映射,包括嵌套结果集。
- 只要使SQL的列名与POJO的属性名保持一致,就阔以进行自动映射而无需任何其他配置
3.2.2. 关于驼峰式命名规则
- 骆峰式命名法就是当变量名或函式名是由一个或多个单字连结在一起,而构成的唯一识别字时,第一个单词以小写字母开始;第二个单词的首字母大写或每一个单词的首字母都采用大写字母
比如sql字段为role_name,pojo属性名为roleName,只要在mapUnderscoreToCameCase设置为true即可。
3.3. 数据库字段与java类型对应关系
在java类中基本类型都会有默认值,所以在实体类中不要使用基本类型。基本类型包括:int,byte,long,short,float,double,char,boolean
3.4. resultMap与resultType的区别
- resultType来设置返回的结构类型时,需要在SQL中为所有列名和属性名不一致的列名设置别名,通过设置别名使最终的查询结果和resultType指定对象的属性名保持一致,进而实现自动映射。
- resultMap来设置结果映射,可以在resultMap中设置property属性和column属性的映射.
resultMap包含的属性如下:
- id:必填,并且唯一
- type:必填,用于查询列所映射到的java对象类型
- extends:选填,可以配置当前的resultMap继承自其他的resultMap,属性值为继承resultMap的id
- autoMapping:选填,值为true或false,用于配置是否启用非映射字段的自动配置功能,该配置可以覆盖全局的autoMappingBehavior配置
property属性或者别名要和对象中属性的名字相同,但是在实际匹配中,MyBatis会先将两者转换为大写形式,然后再判断是否相同。由于大多数数据库不区分大小写,下画线命名方式很常见。
<settings>
<!--value值为true:自动将下划线方式命名的数据库列映射为java对象驼峰式命名属性中-->
<setting name="mapUnderscoreToCamelCase" value="true">
</settings>
3.5. 传递多个参数
当需要有多个参数进行传递时,可以通过下面三种方法进行传递:
- 使用Map接口传递参数,可以使用map接口通过键值对传递多个参数
接口方法定义:
public List<Role> findRoleByMap(Map<String ,Object> parameterMap);
严格来讲,Map适用于所有场景,但是用的不多:一是map是键值对,使用者必须阅读他的键才能明白其作用,二是map不能限定其传输的数据类型,因此业务性质不强,可读性差。
在xml文件中配置时paramterType的属性值设置为map
- 使用注解传递多个参数
可以使用注解定义映射器的参数名称
接口方法:
public List<Role> findRolesByAnnotation(@Param("rolenName") String rolename,@Param("note") String note);
//此时在xml文件配置时不需要给出parameterType属性,自动探索便可
@Param注解的作用是 给参数命名,参数命名后根据名字得到参数值。
- 通过Java Bean传递多个参数
当参数比较多的时候,使用注解传递非常不方便,此时使用Java Bean的方式传递参数
POJO:
public class RoleParams {
private String roleName;
private String note;
/**setter和getter方法**/
}
接口方法:
public List<Role> findRolesByBean(RoleParams roleParams);
3.6. #{}与${}的区别
3.6.1. sql预编译
- 定义
sql预编译指的是数据库驱动在发送sql语句和参数给DBMS(数据库管理系统)之前对sql语句进行编译,这样DBMS在执行sql时,就不需要重新编译。 - 为什么需要预编译
JDBC中使用对象PreparedStatement来抽象预编译语句- 预编译阶段可以优化sql的执行:预编译后的sql多数情况下可以直接执行,DBMS不需要再次编译,越复杂的sql,编译的复杂度将越大,预编译阶段可以合并多个操作为一个操作。
- 预编译语句对象可以重复利用:把一个sql预编译产生后的preparedStatement对象缓存下来,下次对于同一个sql,可以直接使用这个缓存的PreparedState对象。
MyBatis默认情况下,将对所有的sql语句进行预编译
3.6.2. ‘#‘与’$’
- 在动态SQL解析阶段#{}和${}会有不同的表现
- 一是: #{}解析为一个JDBC预编译预编译语句的参数标记符
select * from user where name = #{name};
<!--解析为:-->
select * from user where name = ? ;
<!--#{}被解析为参数占位符-->
- 二是:${}仅仅为一个纯粹的String替换,在动态SQL解析阶段将会进行变量替换
select * from user where name = ${name};
<!--当传入的参数为yq时-->
<!--解析为:-->
select * from user where name = "yq" ;
<!--预编译之前的sql语句已经不包含变量name-->
- 综上所述:${}的变量的替换是在动态解析阶段,而#{}的变量的替换是在DBMS中
- 用法:
- 能使用${}就可以使用#{},防止SQL注入,提高系统的安全性。
- 表名作变量时必须使用${}.因为表名是字符串,使用占位符替换时会带上引号,会导致sql语法错误。
4. Select
<select id="selectById" parameterType="int" resultType="User">
select * from user where id=#{id}
</select>
- id:命名空间中的唯一标识符,用来代替这条语句
- parameterType:传入的参数类型
- resultType:定义当前查询的返回值类型
5. Insert
sqlSessionFactory.openSession()方法默认不会提交插入的数据到数据库中,需要设置为true
5.1. Insert标签常用属性
- id:命名空间中的唯一标识符,用来代替这条语句
- parameType:即将传入的语句参数的完全限定类名或者别名,可选。
- flushCache:默认值为true,任何时候只要与被调用,都会清空一级缓存和二级缓存。
- timeout:设置在抛出异常之前,驱动程序等待数据库返回请求结果的秒数
- useGeneratedKeys:默认值为false,设置为true时,MyBatis会使用JDBC的getGeneratedKeys获取由数据库内部生成的主键。
- KeyProperty:MyBatis通过getGeneratedKeys获取主键值后将要赋值属性名。
- keyColumn:仅对INSERT和UPDATE有用,通过生成的键值设置表中的列名,当主键列不是表中的第一列时需要设置。
5.2. Insert的三种用法
- 一般情况下插入数据:
<!--插入-->
<insert id="insert1">
insert into user (id,age,name) values (#{id},#{age},#{name})
</insert>
- 使用JDBC方式返回主键的值
这种方法适用于主键自增的数据库,使用主键自增时,插入数据库后可能得到自增的主键值,然后再使用这个值进行一些其他的操作.
需要在数据库中进行主键自增的设置
<!--
useGeneratedKeys:默认值为false,如果设置为true,Mybatis会使用JDBC的getGeneratedKeys方法取出由数据库内部生成的主键
keyProperty:通过上面的方法获取主键后要赋值的属性名,此方法中为id
注意:再插入语句中不在写入id字段
-->
<insert id="insert2" useGeneratedKeys="true" KeyProperty="id">
insert into user(age,username) values (#{age},#{username})
</insert>
- 使用selectKey标签返回主键的值
有些数据库不支持主键自增,而是使用序列得到一个值,然后将这个值赋给id,在将数据插入数据库,对于这种情况可以使用标签来获取主键的值,这种方式不仅适用于主键自增的数据库,也适用于不提供主键自增的数据库。
<insert id="insert3">
insert into user (age,name) values ( #{age},#{name})
<selectKey keyColumn="id" resultType="int" keyProperty="id" order="AFTER">
select LAST_INSERT_ID()
</selectKey>
</insert>
keyColumn和KeyPropety的作用和上面的useGeneratedKeys作用一样。
order的设置和使用于数据库有关,在MySql中设置为AFTER,因为当前记录的主键值在insert语句执行成功后才能获得。而在Oracle中,order的值要设置为BEFORE,这是因为Oracle中需要先从序列中获取值,然后将值作为主键插入到数据库中。
6. Delete的用法
根据主键删除的时候,如果主键只有一个字段,可以使用以下方法
<delete id="deleteById">
delete from user where id = #{id}
</delete>