本专栏打算先记录一些深入学习MyBatis后了解到的一些比较好用的操作分享给大家,后续可以对MyBatis框架进行源码解析,希望大家在面试或者工作过程中可以游刃有余。
通过JDBC访问数据库
首先我们应该了解,对于Java而言,与数据库(这里主要针对MySQL数据库)交互的方法,我们第一个想到的就是JDBC,那么通过JDBC进行数据查询需要哪些步骤呢?
// 1. 注册驱动和数据库信息获得connection
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306...","account","password");
// 2.通过connection打开preparedStatement
PreparedStatement ps = connection.prepareStatement("select name, age from employee where id = ?");
ps.setInt(1,1);
//3. 利用preparedStatement来执行sql语句,获取ResultSet
ResultSet resultSet = ps.executeQuery();
//4. 将ResultSet中的结果集转成Java的POJO对象或进行后续操作
while (resultSet.next()){
//执行查询结果后续操作
}
//5. 关闭连接
connection.close();
从上述伪代码中我们可以了解到,使用JDBC进行数据库交互的步骤比较繁琐,有时候我们只是做一些查询操作,没必要如此麻烦,因此MyBatis的出现给我们带来了很多便捷。那么MyBatis有什么特点呢:
通过MyBatis执行数据库查询
通过MyBatis访问数据库的实现步骤:
1. 引入MyBatis的Maven依赖
2. 在mybatis-config.xml配置文件中配置数据源信息
3. 编写EmployeeMapper.java文件
4. 编写EmployeeMapper.xml配置文件,编写SQL,建立起Employee与tb_employee表的映射关系
优点:
1. MyBstis可以实现动态SQL,根据入参的实际情况B来进行SQL执行。
2. 动态映射,MyBatis可以在配置文件中通过配置来决定SQL的映射规则,可以自动映射。
3. 面向接口编程:一般来说,只需要某个DO (Database Object) 的mapper接口以及对应SQL的xml文件即可运行,与Java代码解耦。
此外,MyBatis底层也是基于JDBC来实现的,关于这里在后续源码解析部分进行详细介绍。
本文不会对MyBatis的安装及基本使用进行详细介绍,只是会介绍一些个人认为在日常工作中对我们有帮助的使用技巧。
MyBatis的使用技巧
自动映射
在mybatis-config.xml文件中进行自动映射配置
<settings>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<!-- 这里必须要配置下划线转驼峰,否则失效 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
自动映射autoMappingBehavior共有三个值:
NONE:取消自动映射
PARTIAL:自动映射,不会映射嵌套结果集,MyBatis默认值
FULL:会自动映射任意复杂的结果集(无论是否嵌套)
使用自动映射时,必须开启下划线转驼峰的自动转换!!!
主键回填
我们在工作开发中经常会遇到这种场景:在tb_employee员工表中新增一个员工信息,并且在tb_contact联系方式表中添加对应的联系方式,两张表要用employee_id主键做关联,如果员工表主键是自增的,那么我们提前获取主键id就很困难,那么会怎么实现呢?一种解决办法是先插入员工表数据,再通过其他唯一性条件将这条数据查出来获取主键id,最后关联到contact表,这样操作相当不优雅,而主键回填就帮我们轻松解决:
<insert id="insertNewEmployee" useGeneratedKeys="true">
insert into tb_employee (name, age) values ("Judy", 18)
</insert>
在SQL的xml对应insert语句中添加主键回填标签,并设置为true,即可完美解决上述问题,我们在mapper文件中对其结果进行接收即可获取主键id值
级联
MyBatis提供了两种级联方式。分别是association和collection,其中前者用于解决一对一的关系,表示一个对象最多有一个关联对象,例如员工信息和对应籍贯信息;而后者顾名思义,用于解决一对多的关联关系,表示一个对象可以有多个关联,例如员工信息对应不同的的联系方式。
association
实例:获取某一员工基本信息和毕业院校信息
员工信息(主表)
@Data
public class Employee {
private Long id;
private String name;
private Integer age;
private Long graduateId;
private GraduateInfo graduateInfo;
}
学校信息
@Data
public class GraduateInfo {
private Long id;
private Long graduateId;
private String graduatedSchoolName;
}
查询学校信息GraduateInfoMapper.xml
<select id="getGraduateInfo" parameterType="long" resultType="graduateInfo">
select
id, graduate_id, graduated_school_name
from
graduate_info
where
graduate_id = #{graduateId}
</select>
查询员工信息EmployeeMapper.xmlvv
<resultMap id="employeeAndGraduateMap" type="employee">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
<result column="graduate_id" property="graduateId"/>
<association property="graduateInfo" column="graduate_id"
select="mybatis.mapper.GraduateInfoMapper.getGraduateInfo"/>
</resultMap>
<select id="getEmployeeInfoAndGraduateInfoById"
parameterType="long" resultMap="employeeAndGraduateMap">
select
id, name, age, graduate_id
from
employee
where id = #{id}
</select>
利用graduateId将两张表关联起来,由MyBatis解析查询数据,并将结果封装起来,这样我们就无需在Java代码中分别查询员工信息和学校信息,最后再自行封装。
collection
@Data
public class Employee {
private Long id;
private String name;
private Integer age;
// 使用list进行接收
private List<Contact> contacts;
}
collection和association的xml配置区别不大,在仅修改标签即可,大家抽空可以尝试实现,
#与$的区别及SQL注入
我们会过头看一下JDBC的例子,在进行有传参的SQL执行时,JDBC通过使用?号作为预编译SQL中参数的占位符,而MyBatis底层也是JDBC的封装,使用#{}来代替JDBC的占位符。
${}仅仅是为了做字符串替换,无其他含义,而在ToC的场景或者外部条件下很容易产生SQL注入,那我们来说一下什么是SQL注入:
首先我们有一个场景:
在订单系统中我们只有部分订单的查看权限,当我们对某个订单进行查询,传入String类型orderId参数:30
通过系统进行数据库查询,那么在MyBatis中执行SQL语句时,#号和$号的SQL组装结果如下:
<select id="getOrderById" resultSetType="orderData">
select order_id from tb_order where id = #{orderId}
<!-- 实际SQL为: select order_id from tb_order where id = '30'-->
</select>
<select id="getOrderById" resultSetType="orderData">
select order_id from tb_order where id = ${orderId}
<!-- 实际SQL为: select order_id from tb_order where id = 30-->
</select>
表面看起来区别不大,且能正确查询数据,如果我们的传入参数为:30 or 1 = 1,这样SQL的组装结果如下:
<select id="getOrderById" resultSetType="orderData">
select order_id from tb_order where id = #{orderId}
<!-- 实际SQL为: select order_id from tb_order where id = '30 or 1 = 1'-->
</select>
<select id="getOrderById" resultSetType="orderData">
select order_id from tb_order where id = ${orderId}
<!-- 实际SQL为: select order_id from tb_order where id = 30 or 1 = 1-->
</select>
使用#号查询数据为空,但是使用$号竟然查询出了全部数据!这是我们所不能容忍的,
所谓SQL注入就是指web应用对用户输入的数据合法性过滤不严,导致攻击者可以在查询语句结尾添加额外的SQL语句实现非法操作,因此对于$号,我们应该谨慎使用!
缓存机制
MyBatis默认开启了一级缓存(也叫本地缓存localCache),当同一个SqlSession对象调用同一个Mapper方法时,MyBatis第一次会从数据库获取数据后将其存入一级缓存中,如果没有声明查询时需要刷新缓存并且缓存没过期的情况下,后续查询只会取当前缓存中的数据。
一级缓存什么时候刷新:
1. 执行数据更新操作,包括insert、update和delete;
2. 显式使用sqlSession.clearCache()方法会清空缓存;
3. 执行sqlSession.sqlcommit() 方法提交事务;
4. 执行sqlSession.close()关闭会话;
如需开启二级缓存,需要在对应Mapper文件中添加<cache/>标签,或者在对应DO类上面添加@CacheNamespace(blocking = true)来开启缓存, 同时要注意:查询数据时要执行SQL Session.commit()时二级缓存才会生效,其中POJO必须实现Serializable接口 ,具体原因在后续源码解析过程中会给大家详细介绍。
本次先给大家介绍这些常用操作,如果大家在工作中有哪些功能使用方便,也可以在评论区提供出来,感谢大家分享。