Web基础之Mybatis
Web基础之Mybatis
对比JdbcTempalte,mybatis才能称得上是框架,JdbcTempalte顶多算是工具类,同时,对比Hibernate,Mybatis又有更多的灵活性,算是一种折中方案。
特点:
- 支持自定义SQL、存储过程、及高级映射
- 实现自动对SQL的参数设置
- 实现自动对结果集进行解析和封装
- 通过XML或者注解进行配置和映射,大大减少代码量
- 数据源的连接信息通过配置文件进行配置
mybatis整体结构:
主配置文件
mybatis-config.xml
依赖
主配置文件
映射文件:
UserMapper.xml
使用slf4j12记录日志:
log4j.properties
依赖
配置文件
测试方法:
@Test
大致就是通过SqlSessionFactoryBuilder类获得sql会话工厂,通过sqlSession执行sql语句,而要执行的语句及其映射bean都已经配置在xml里面。需要注意的是mybatis默认开启事务,所以执行增删改时需要手动提交。
Dao接口映射
mybatis可以直接映射Dao接口,而不必写其实现类(其实是mybatis帮我们实现了啦)
Dao接口:
UserMapper
映射配置文件
Tips:在IDEA中可以使用Ctrl + Shift + T
快速创建测试用例
数据库字段和Bean属性名称不一致问题
- sql语句查询使用别名
- 在主配置文件中开启驼峰匹配:
- 自定义resultMap映射
mybatis主配置
配置文档的顶层结构如下:
具体可以参考官方文档
这里介绍几个常用的属性。
properties
属性:properties,可以定义变量,然后使用${变量名}
来取得其值,如:
jdbc.properties内容:
然后便可以通过${driverClass}
来配置驱动了。
settings
包含<setting>
标签,有name
和value
属性
mapUnderscoreToCamelCase
驼峰匹配(默认关闭)
lazyLoadingEnabled
延迟加载(默认关闭)
cacheEnabled
二级缓存(默认开启)
autoMappingBehavior
自动映射规则:
name属性 | 描述 | value属性 | 默认value |
---|---|---|---|
autoMappingBehavior | 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套)。 | NONE, PARTIAL, FULL | PARTIAL |
什么沙雕排版😅
typeAliases
类型别名是为 Java 类型设置一个短的名字。 它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。
第一种是直接配置一个类的别名,第二种是配置扫描一个包下的所有类
或者注解方式配置别名:
以及数据类型的别名(不区分大小写,基本数据类型特殊命名):
数据类型别名
别名 | 映射的类型 |
---|---|
_byte | byte |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
object | Object |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
iterator | Iterator |
environments
Mybatis可以配置多个环境,但是一个SQLSessionFactory只对应一个环境
可以通过build方法的重载指定环境:
如果忽略了环境参数,那么默认环境将会被加载,如下所示:
mappers
mapper的作用是告诉mybatis去哪里照执行的SQL语句,可以通过下面四种方式:
相对路径的xml文件引用(resources目录):
绝对路径的xml文件引用(不推荐):
通过接口路径:
此方式条件:
- 映射文件和mapper接口在同一个目录下(或者resources下的同目录)
- 文件名一致
- 映射文件的namespace必须和mapper接口的全路径保持一致
通过接口所在包的路径
注意事项
使用动态代理的方式实现接口映射时,mapper.xml文件的命名空间必须是接口的全限定名,并且不能使用typeAliases
别名,因为别名是针对Java Bean的
前两种为直接指定xml文件位置,因此对xml路径没有什么要求。
后两种为指定接口然后寻找xml文件,因此xml需要在和接口相同的路径(相对resources),并且!idea中resources目录不能使用.
(点)来建立多级目录,需要使用/
或者\
来建立多级目录!!!
mapper xml 映射文件
映射文件的结构:
- cache – 对给定命名空间的缓存配置。
- cache-ref – 对其他命名空间缓存配置的引用。
- resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
parameterMap – 已被废弃!老式风格的参数映射。更好的办法是使用内联参数,此元素可能在将来被移除。文档中不会介绍此元素。- sql – 可被其他语句引用的可重用语句块。
- insert – 映射插入语句
- update – 映射更新语句
- delete – 映射删除语句
- select – 映射查询语句
select
示例:
- id:在命名空间中唯一的标识符,可以被用来引用这条语句。
- resultType:返回类型
- parameterType:参数类型,可以省略
- resultMap:结果映射,mybatis最强大的特性,不可以和resultType同时使用
insert
示例:
- id:唯一标识符
- useGeneratedKeys:开启主键自增回显,将自增长的主键值回显到形参中(即封装到User对象中)[可选]
- keyColumn:数据库中主键的字段名称 [可选]
- keyProperty:pojo中主键对应的属性 [可选]
- parameterType:参数类型 [可选]
update & delete
示例:
parameterType属性
CRUD都有个parameterType标签,用来指定接受的参数类型。
接收参数有两种方式:
#{ }
预编译,类似占位符${ }
非预编译,直接sql拼接,不能防止SQL注入,一般用来接收表名等属性
参数类型有三种:
- 基本数据类型
- HashMap(使用方式和pojo类似)
- Pojo自定义包装类
基本数据类型
当sql方法的参数是多个时:
例如queryUserByUserNameAndPassword(String username, String password)
这种两个参数时,可以这样接收参数:
不能见名知意的变量不是好方法,所以我们的解决方案是添加@Param
注解:
这样就可以见名知意了。
HashMap参数
示例:
xml中和注解用法类似:
Pojo
mapper.xml用法不变,xml是通过Getter方法来获取值的。
${}
的用法
一个参数时,默认情况下使用${value}
接收数据。但是这样不能见名知意。
同样使用@Param()
注解。
${ }
和#{ }
#{ }
- 预编译
- 编译成占位符
- 可以防止sql注入
- 自动判断数据类型(参数时字符串时会自动加字符串)
- 一个参数时,可以使用任意参数名称进行接收(即#{xxx})
${ }
SQL拼接(不能防止SQL注入)- 非预编译
- sql的直接拼接
- 不能防止sql注入
- 需要判断数据类型,如果是字符串,需要手动添加引号。
- 一个参数时,参数名称必须是value,才能接收参数。
$
还有一个问题是,在主配置文件中,使用${driver}
获取资源文件的驱动或者url等数据,如果用户的password属性和资源文件中的password属性同名时,此时会读取资源文件中的password而不会使用传入的参数password,解决方法是在资源文件中的属性都加入前缀:
当然,主配置文件里面的引用也要修改。
ResultMap
resultMap是mybatis中最强大的特性,可以很方便的解决下面两个问题:
- Pojo属性名和表结构字段名不一致(有时候不只是驼峰格式)
- 高级查询(主要是这个)
简单映射示例:
在映射文件中配置自定义ResultMap:
autoMapping
属性:
- 为true时:resultMap中的没有配置的字段会自动对应。如果不配置,则默认为true。
- 为false时:只针对resultMap中已经配置的字段作映射。
在查询语句中使用自定义映射:
高级查询
一对一映射:
当订单(Order)对象内有用户(User)属性时,之前的情况我们是不能一次查询出来的,但是有了映射便可以很方便的查询:
一对多映射
当一个订单内有多个信息时,即Order类中持有List<OrderDetail>
,便可以进行一对多映射。
这里的订单可以理解为多个商品一次下单,这一订单中有多个OrderDetail,每个OrderDetail对应一个商品
注意,当表数量较多时,需要指定不同表主键的别名来区分。
多对多映射
每个OrderDetail对应一个商品,这时便可以添加映射:
继承
从上面的代码可以看出这样映射虽然很方便,但是代码存在冗余的情况:
图中的代码我们已经在其他映射中配置过了,当后面的映射需要这一段时,我们便可以使用继承。
修改后的第三个映射的resultMap为:
延迟加载
延迟加载是指当我们需要哪部分数据时,然后再去查。
上面的几个映射,每次查询时会一股脑将数据全部查询出来,即使不需要的数据也会查询(因为只有一条语句)。当我们查询订单时,需要用户信息的时候再去查,因此SQL语句需要拆成两条。
主配置文件中开启延迟加载:
延迟加载示例:
如果报错需要添加cglib依赖(3.3以上不需要添加此依赖):
SQL 片段
对于使用很频繁的SQL语句,可以单独抽离出来进行复用。
在同一个mapper.xml中:
但是这样只能在一个mapper文件中使用,因此我们可以把经常使用的写入在一个mapper文件中:
CommonSQL.xml文件:
在主配置中引入:
然后就可以在mapper中使用:
即refid = "namespace.sqlID"
变一下即可。
XML 特殊字符
感觉这个好麻烦啊,因为这些字符在SQL中出现的频率太高了
符号 | 转义 |
---|---|
\< | < |
\> | > |
\& | & |
\' | ' |
\" | " |
或者使用CDATA:<![CDATA[]]>
真是反人类。。。
动态SQL
mybatis中也可以动态拼接SQL语句(但是在xml中写SQL太难受了),支持ognl表达式(Struts不就是因为这东西才被黑客利用爆出漏洞死掉的么,还用。。。)
if
示例:
上面的SQL可以通过用户名或者ID来查询。
choose, when, otherwise
相当于switch, case, default,只不过自动break,一旦有一个when成立,后续的when都不再执行。
示例:
上面的SQL意思是如果提供了id就用id查询,没提供id就用用户名查询,都没有的话则查询所有的激活用户。
where, set, trim
第一个查询如果把性别也改为动态的:
如果sex参数为空的话这条SQL语句就变成了这样:
有点逗比是不是,此时<where>
标签的作用就体现出来了:
where 元素只会在至少有一个子元素的条件返回 SQL 子句的情况下才去插入“WHERE”子句。而且,若语句的开头为“AND”或“OR”,where 元素也会将它们去除。
如果 where 元素没有按正常套路出牌,我们可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:
<set>
标签和where类似:
set 元素会动态前置 SET 关键字,同时也会删掉无关的逗号,因为用了条件语句之后很可能就会在生成的 SQL 语句的后面留下这些逗号。
上面的句子就相当于:
foreach
动态 SQL 的另外一个常用的操作需求是对一个集合进行遍历,通常是在构建 IN 条件语句的时候。比如:
可以迭代Collection、数组、Map,当参数为Map时,index为键,item为值。
缓存
一级缓存:
缓存是sqlSession级别,默认开启(无法关闭?),可以使用session.clearCache()
方法清除缓存。对于同一条数据再次查询会查询缓存里的数据。需要注意的是增、删、改语句都会清除缓存,即使是不同数据。
二级缓存:
二级缓存需要Pojo对象实现Serializable接口,可以实现不同sqlSession间公用缓存。当第一个sqlSession查询一条数据后调用sqlSession.close()
方法会将数据添加到二级缓存,第二个sqlSession再次查询同一数据时会使用缓存。(数据增删改同样会情况二级缓存)
开启二级缓存:
注解方式使用Mybatis
注解需要在主配置文件中添加包扫描。
@Param
给参数添加别名(估计是因为反射不能获取接口中声明的局部变量名称)
示例:
@Select、@Delete、@Update、@Insert
@Results注解别名映射
注解高级查询
一对一映射
UserMapper接口:
一对多映射
OrderDetailMapper接口:
多对多映射
多对多只需要在被引用的一对多方法里添加一对一即可:
注解延迟加载
在@One注解里添加fetchType
属性:
fetchType属性会覆盖全局属性,可选值有:
- FetchType.LAZY:延迟加载
- FetchType.EAGER:立即加载
- FetchType.DEFAULT:默认属性(即全局属性)
注解动态SQL
注解方式使用动态SQL的话需要一个类来专门构建SQL语句:
接口中的方法为:
除了SelectProvider还有:
- @InsertProvider
- @UpdateProvider
- @DeleteProvider
- @SelectProvider
按需添加即可。
如果@SelectProvider描述的抽象方法没有使用@Param给变量添加别名,并且声明了多个变量,那么提供SQL的类的方法参数需要和接口中的方法一样,也就是声明出所有的变量:
提供SQL的类的方法也需要声明为接口同样的参数:
如果接口中的抽象方法使用了@Param参数,那么类中提供SQL的方法便可以用哪个参数声明哪个参数:
提供SQL的类中的方法便可以这样写:
如果Mybatis的版本在3.5.1以后,可以将这样简化:
映射的接口:
提供SQL的类: