引言
谈论起“持久化”一词,天天操作数据库的大家肯定不会陌生。
持久化中,备受开发者瞩目的就是两大巨头:Hibernate
与MyBatis
,许多开发者也常常拿二者对比。去Google
上,都是类似两者互相对比的文章。
Hibernate
与MyBatis
,就像java
和php
一样,双方似有打架之势。
虽然我是使用Hibernate
的工程师,但是我并不否认MyBatis
,两者都优秀,但一切都是业务优先。
学习了潘老师的《SpringBoot+Angular入门实例教程》后,也想到了最原始的JDBC
。
今天,我们就好好地从古至今,聊聊持久化技术的演进与对比。
持久化
远古的JDBC
JDBC
:Java Database Connectivity
,简称JDBC
。是Java
语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。JDBC
是面向关系型数据库的。
每一位Java
程序员可能都经历过被JDBC
支配的恐惧。
下面是我在Java
实验课上写过的JDBC
代码,实现的功能很简单,就是要将一个Student
对象保存到数据库中的student
表中。
/**
* 持久化学生实体
*/
private static void persistStudent(Student student) {
try {
Class.forName("com.mysql.jdbc.Driver");
// 数据库连接配置
String url = "jdbc:mysql://127.0.0.1:7777/java?characterEncoding=utf-8";
// 获取连接
Connection connection = DriverManager.getConnection(url, "root", "root");
// 获取语句
Statement statement = connection.createStatement();
// 生成SQL语句
String SQL = student.toSQLString();
// 执行语句
statement.executeUpdate(SQL);
} catch (SQLException e) {
System.out.println("ERROR: " + e.getMessage());
} catch (ClassNotFoundException e) {
System.out.println("ERROR: " + e.getMessage());
} finally {
statement.close();
connection.close();
}
}
核心的功能其实就一行:statement.executeUpdate(SQL)
,我只想执行一条INSERT
语句保证数据的持久化。
可是在JDBC
中却需要实现加载MySQL
驱动,获取Connection
,获取Statement
,才能执行SQL
,执行之后还需要手动释放资源,不可谓不麻烦。
程序员最讨厌写重复的代码,就像我感觉从华软平台移植重复代码到试题平台很枯燥一样,所以这些“模版式”的代码需要封装。
封装工具类
我们都是平凡人,我们能想到的,肯定早就有人想到了。
Spring
封装了JDBC
,提供了JdbcTemplate
。
另一个比较出名的是Apache
封装的DBUtils
。
使用了JdbcTemplate
后,我们无需再编写模版式的Connection
、Statement
、try ... catch ... finally
等代码。教程中使用了JdbcTemplate
查询教师表,代码长这样:
@GetMapping
public List<Teacher> getAll() {
/* 初始化不固定大小的数组 */
List<Teacher> teachers = new ArrayList<>();
/* 定义实现了RowCallbackHandler接口的对象 */
RowCallbackHandler rowCallbackHandler = new RowCallbackHandler() {
/**
* 该方法用于执行jdbcTemplate.query后的回调,每行数据回调1次。比如Teacher表中有两行数据,则回调此方法两次。
*
* @param resultSet 查询结果,每次一行
* @throws SQLException 查询出错时,将抛出此异常,暂时不处理。
*/
@Override
public void processRow(ResultSet resultSet) throws SQLException {
Teacher teacher = new Teacher();
/* 获取字段id,并转换为Long类型返回 */
teacher.setId(resultSet.getLong("id"));
/* 获取字段name,并转换为String类型返回 */
teacher.setName(resultSet.getString("name"));
/* 获取字段sex,并转换为布尔类型返回 */
teacher.setSex(resultSet.getBoolean("sex"));
teacher.setUsername(resultSet.getString("username"));
teacher.setEmail(resultSet.getString("email"));
teacher.setCreateTime(resultSet.getLong("create_time"));
teacher.setUpdateTime(resultSet.getLong("update_time"));
/* 将得到的teacher添加到要返回的数组中 */
teachers.add(teacher);
}
};
/* 定义查询字符串 */
String query = "select id, name, sex, username, email, create_time, update_time from teacher";
/* 使用query进行查询,并把查询的结果通过调用rowCallbackHandler.processRow()方法传递给rowCallbackHandler对象 */
jdbcTemplate.query(query, rowCallbackHandler);
return teachers;
}
思考问题:既然可以通过ResultSet
获取查询结果,这里为什么Spring
封装的query
方法不直接返回ResultSet
对象,而要设计这样一个回调的对象呢?
提示,JdbcTemplate
查询的核心源码如下:
@Override
@Nullable
public T doInStatement(Statement stmt) throws SQLException {
ResultSet rs = null;
try {
rs = stmt.executeQuery(sql);
return rse.extractData(rs);
}
finally {
JdbcUtils.closeResultSet(rs);
}
}
ResultSet
用起来很讨厌是不是?需要手动地去获取字段并设置到对象中,JdbcTemplate
提高了开发效率,但是提高地不明显,能不能再简单一点呢?
ORM
为了避免编写大量这样的与业务无关的代码,设计了ORM
思想。
teacher.setName(resultSet.getString("name"));
teacher.setSex(resultSet.getBoolean("sex"));
teacher.setUsername(resultSet.getString("username"));
teacher.setEmail(resultSet.getString("email"));
ORM
:即对象关系映射。将对象与数据表进行关联,我们无需关注这类不关联业务的冗余代码,操作对象,即操作数据表。
半自动化ORM
半自动化的ORM
框架,就是炙手可热的MyBatis
。
我简单地去学习了以下MyBatis
,毕竟这么多公司使用,肯定有他们的道理,如果足够优秀,也可以考虑使用。可是结果却有些令人失望。
打开官网学习,这应该算是我见过的最寒酸的著名开源框架的官网了,里面的内容也不详细。
官网的例子不够详细,我又参阅了许多MyBatis
的博文进行学习。
开启数据库字段下划线到对象命名驼峰的配置。
mybatis.configuration.map-underscore-to-camel-case=true
还是经典的教师、班级、学生的关系:
public class Teacher {
private Long id;
private String name;
private Boolean sex;
private String username;
private String email;
private Long createTime;
private Long updateTime;
}
public class Klass {
private Long id;
private String name;
private Teacher teacher;
private List<Student> students;
}
public class Student {
private Long id;
private String name;
}
教师的CRUD
单表查询:
@Mapper
public interface TeacherMapper {
@Select("SELECT * FROM teacher")
List<Teacher> getAll();
@Select("SELECT * FROM teacher WHERE id = #{id}")
Teacher get(Long id);
@Select("SELECT * FROM teacher WHERE username = #{username}")
Teacher findByUsername(String username);
@Insert("INSERT INTO teacher(name, sex, username, email, create_time, update_time) VALUES(#{name}, #{sex}, #{username}, #{email}, #{createTime}, #{updateTime})")
@Options(useGeneratedKeys = true, keyProperty = "id")
void insert(Teacher teacher);
@Update("UPDATE teacher SET name=#{name}, sex=#{sex}, email=#{email}, update_time=#{updateTime} WHERE id=#{id}")
void update(Teacher teacher);
@Delete("DELETE FROM teacher WHERE id=#{id}")
void delete(Long id);
}
关联查询:
@Mapper
public interface KlassMapper {
@Select("SELECT * FROM klass")
@Results({
@Result(column = "teacher_id", property = "teacher", one = @One(select = "club.yunzhi.mybatis.mapper.TeacherMapper.get")),
@Result(column = "id", property = "students", many = @Many(select = "club.yunzhi.mybatis.mapper.StudentMapper.getAllByKlassId"))
})
List<Klass> getAll();
}
关联中用到的StudentMapper
子查询:
@Mapper
public interface StudentMapper {
@Select("SELECT * FROM student WHERE klass_id=#{klassId}")
List<Student> getAllByKlassId(Long klassId);
}
也学了一个晚上了,虽然二级缓存之类的高级特性还没学习呢,但是对MyBatis
也算是又一个大体的了解了。
业务是老大,所有技术都是服务于业务的。程序员应该关注业务与实际问题,我是不太喜欢去写SQL
的,SQL
应该是DBA
去专业学习并优化的,MyBatis
看起来更像是给DBA
用的框架一样。
全自动化ORM
当一个系统设计完成之后,其他的工作就是搬砖。
搬砖当然是越简单越好,不编写SQL
,Hibernate
走起。
public interface TeacherRepository extends CrudRepository<Teacher, Long> {
}
完全基于对象,更加注重业务。
怎么选?
好多人都是“你看支付宝用MyBatis
,那我也用MyBatis
”。
这是StackOverflow
上一个十年前的关于两者对比讨论的话题:https://stackoverflow.com/questions/1984548/hibernate-vs-ibatis
最终的讨论结果如下:
iBatis
和Hibernate
是完全不同的事物(iBatis
是MyBatis
的前身)。
如果以对象为中心,那Hibernate
更好;如果以数据库为中心,那iBatis
更好。
如果你设计系统架构,没有高并发的需求,Hibernate
最合适,对象模型会使得代码非常简洁,但代价巨大。
如果你接手了一个遗留下来的数据库,并且需要编写复杂的SQL
查询,那iBatis
更合适。
总结一句话就是性能问题:Hibernate
方便,但是会有性能损耗;MyBatis
有性能优势。
总结
都说MyBatis
适合高并发,但高并发又岂是一个MyBatis
就能囊括的?
业务是老大,如果真的碰到了高并发需求,那又是我们进步的时候了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。