引言
谈论起“持久化”一词,天天操作数据库的大家肯定不会陌生。
持久化中,备受开发者瞩目的就是两大巨头: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());
}
}
核心的功能其实就一行: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
用起来很讨厌是不是?需要手动地去获取字段并设置到对象中,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
就能囊括的?
业务是老大,如果真的碰到了高并发需求,那又是我们进步的时候了。