陈晨_软件五千言

陈晨_软件五千言

前言

最早接触Java的web开发框架就是SSH,其中的H就是Hibernate。Hibernate作为最出名的Java的ORM框架,现在的版本已经到了5.3.10.Final,6.0.0.Alpha2。围绕数据持久化或者DAL层也发展了多种工具,Hibernate Validator目前也是在很多的企业框架中被用作数据有效性检查。
接下来还有用过JPA来实现ORM。JPA全称Java Persistence API,是JSR-220(EJB3.0)规范的一部分,在JSR-220中规定实体对象(EntityBean)由JPA进行支持。在我的使用中,实际上底层实现仍然使用了Hibernate,只是标注或者操作类都是使用了JPA的接口标准而没有直接使用Hibernate。
Hibernate具有强大的ORM封装能力,极大的简化了CUD的操作,而且无需做太多的配置,使用标准注解就能解决很多问题。简单的查询操作也不在话下,多级关联、延迟查询等丰富特性基本上也可以说是极大覆盖了开发过程的各种需求。但是实际上在这么多团队中,很多人的反馈是这样的:“Hibernate的ORM确实很方便,但是在一些特殊条件下很难用,比如复杂查询就很难控制语句生成的效率”。Hibernate有三种查询方式:HQL、Criteria、Native Sql。确实在复杂查询下,如果使用Criteria,确实能够拼出想要的语句,但是可能对于其中的方法要非常熟悉,学习曲线很高,没办法做到团队中每个成员都能数量轻易的掌握,而且后期的审核很困难无法直接看清逻辑。HQL和NativeSql对我的感觉,似乎回到了JSP时代,HTML和Java代码混写,很难忍受。这时候大家找到了另外的框架Mybatis。

ORM框架现状

截止(2019/5/27)Mybatis的Star是10806,UsedBy140381;Hibernate的Star是3817,UsedBy157879。看使用量Hibernate和Mybatis其实已经差不多了,实际企业开发中,Mybatis可能还会更多一些。

Mybatis优缺点

Mybatis放弃了Hibernate的强封装,主要包含了映射的部分,而且放弃了自动解析生成Sql,而直接使用用户XML配置Sql的方式,只是在Sql的拼接上提供了一些标签来避免重复代码。这样的讨巧之处在于:

  • 门槛降低:开发人员不需要了解框架的内部语法,只需要了解Sql语法即可。
  • 可读性提高:审核代码时,很容易的就能看清楚多级关联以及关联使用的条件、语法是否正确。
  • 维护方便:当查询语法出错时,Mybatis只需要修改XML,而Hibernate则需要修改代码重新编译,操作相对复杂。

缺点也有几处:

  • 简单操作复杂:由于放弃了自动解析生成Sql,所以普通的CUD也需要手动编写Sql
  • 实体映射复杂:必须在XML文件中配置大量的字段映射
  • SqlMapper中的sql id风险:由于XML中的sql id是一个字符串,只能复制粘贴出来,所以出错的几率也比较大。

实际上,针对上面缺点,Mybatis也提供了解决方案。
先说sql id的,在新的Mybatis中,实际上已经采用了面向接口的编码方式,如下面的例子:

接口类

public interface UserMapper {
    public User findUserById(Integer id);
}

mapper的xml文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.software5000.mapper.dao.UserMapper">
    <select id="findUserById" resultType="com.software5000.entity.User" parameterType="int" >
        select * from user where id =#{id}
    </select>
</mapper>

这样就可以直接调用sql语句:

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.findUserById(1);

再说映射复杂的,Mybatis提供了MyBatis Generator作为解决方案,一个命令生成下列内容:

  • 匹配表结构的Java POJOs。可能包括:
    • 表结构中主键字段(如果存在主键)
    • 表结构中的非主键字段(除了BLOB字段)
    • 表结构中的BLOB字段(如果有BLOB字段)
    • 动态查询、更新和删除的接口类

自动生成也能够合理的生成类之间的继承关系。注意生成器也能够定义生产不同的POJO层次 - 例如如果你想要可以为每个表格生成一个单独的领域对象。

  • Mybatis/iBATIS兼容的SQL MAP的XML文件。按照配置针对每个表生成简单的增删改查的SQL方法。生成的SQL包括:
    • 插入
    • 按主键更新
    • 使用example更新 (使用动态的where条件)
    • 按主键删除
    • 使用example删除 (使用动态的where条件)
    • 按主键查询
    • 使用example查询 (使用动态的where条件)
    • 使用example统计

生成的SQL语句取决于表格的结构(例如,表格如果没有主键,就不会生成按主键更新的方法)

Java客户端生成类能够合理使用上面的对象。生成的Java客户端类也是有选择性的。

  • 使用Mybatis 3.x的会生成:
    • 一个mapper接口和XML中的语句id相同,用于直接调用。
  • 使用iBATIS 2.x的会生成:
    • 适用于Spring框架的DAO层
    • 使用IBATIS SQL映射API的DAO层。这些DAO可以使用两种方式:使用构造函数提供SqlMapClient,或者通过注入方式提供。
    • DAOs that conform to the iBATIS DAO Framework (an optional part of iBATIS, this framework is now deprecated and we suggest that you use the Spring framework instead)
    • iBATIS的DAO框架符合的DAO层(iBATIS的一个额外部分,但是现在这个框架已经失效了,所以建议使用Spring DAO的框架代替)

但是,我对于MyBatis Generator的态度是坚定的反对。原因是我认为自动生成违反了简单的原则,“如无必要,勿增实体”。自动生成的可复用性和可读性一定是比较差的。我觉得最好的代码就是不存在的代码。因此我希望能够有一个简单框架在Mybatis之上,接入简单无侵入,能够提供基本的增删改查方法。这就是下面的 simple-orm-mybatis 。

simple-orm-mybatis设计思路介绍

simple-orm-mybatis设计的初衷就是希望提供一个简单无侵入,而且无需编写或者生成任何代码就可以使用直接操作对象的方式来进行增删改查的操作。要实现这样的要求,主要是两个主要技术点:

  1. 利用反射机制对应实体对象与数据库结构
  2. 解析对象并且生成对应操作的SQL语句

第一点核心就是反射,设计要点如下:

  • Java对象与数据库结构的对应规则
  • 虚字段(无数据库对应字段)的处理
  • 考虑多层继承的对象解析
  • 值的设置与获取方式

第二点核心在于SQL解析,设计要点如下:

  • 根据不同入口区分基本CRUD语法结构
  • 字段(列名)需要分为值字段与查询字段两类
  • 更新操作的时候,Null,空,有值的区分
  • 支持多种匹配操作符(大于、小于、Like等)

simple-orm-mybatis使用说明

  1. 首先引入依赖,项目已经发布在Maven Central上,可以直接引入。
<dependency>
    <groupId>com.software5000</groupId>
    <artifactId>simple-orm-mybatis</artifactId>
    <version>1.0.2</version>
</dependency>
  1. 接着需要将 BaseDaoMapper.xml文件放在你的 mapper 的扫描文件夹内。

  2. 最后需要在你的代码中添加一个 BaseDao 实现类,用于提供数据库操作服务(注意:需要在spring的扫描包内,因为需要注入某些属性),整个复制即可,类名可以改为你自己需要的名字
import com.software5000.base.BaseDao;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * 整个类以 <code>org.mybatis:mybatis-spring:2.0.0</code> 的 <code>org.mybatis.spring.supportSqlSessionDaoSupport</code>
 * 为参考编写而成
 */
@Component
public class MyBaseDao extends BaseDao {
    private SqlSessionTemplate sqlSessionTemplate;

    public MyBaseDao() {
            // 在默认构造函数中设置 数据库是否蛇形, 数据库格式大小写, 通用忽略的字段名称
            this.initConfig(true,false,"serialVersionUID");
    }

    @Resource
    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
            this.sqlSessionTemplate = this.createSqlSessionTemplate(sqlSessionFactory);
        }
    }

    protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Override
    public SqlSession getSqlSession() {
        return this.sqlSessionTemplate;
    }
}

simple-orm-mybatis实际使用

这里给了一个单元测试的例子,实际一般是在service层直接使用,无需添加任何代码。
示例中的 DailyRecord是一个普通实体类,没有任何继承。

// 获取启动类,加载配置,确定装载 Spring 程序的装载方法,它回去寻找 主配置启动类(被 @SpringBootApplication 注解的)
@SpringBootTest
// 让 JUnit 运行 Spring 的测试环境, 获得 Spring 环境的上下文的支持
@RunWith(SpringRunner.class)
public class BaseDaoTest {

    @Autowired
    private MyBaseDao myBaseDao;

    @Test
    public void testBaseDao(){
        DailyRecord dailyRecord = new DailyRecord();
        List<DailyRecord> dailyRecords = myBaseDao.selectEntity(dailyRecord);
        System.out.println(dailyRecords.size());
    }
}

推荐最佳实践

虽说整体的设计基于无侵入,基本没有任何前提,但是还是有一些推荐的实践希望大家能够去尝试:


1、 数据结构设计建议包含int类型的自增主键设计,名称叫id。
原因:很多时候我们的业务主键是有一套特定规则,但是很有可能面对修改,所以底层关联主键统一使用id关联在后期面对修改时影响较小。
弊端:
1. 如mysql中int最长2147483647,考虑到自增id可能会有跳过不连续的情况,需要考虑实际可用的存储量。不过大部分的业务表是到不了这个数量级的。
2. mysql的InnoDB有自增主键锁表的问题,超大并发插入可能会影响效率。不过在5.1.22有提供了改进的策略。


2、 数据结构与实体结构尽量符合统一转换规则。
原因:这样研发过程中,实体与数据库的映射会比较简单,无需大量的自定义。建议的规则有三类:
- 两边都使用驼峰。
- 实体使用驼峰,数据库使用全小写蛇形
- 实体使用驼峰,数据库使用全大写蛇形


3、 代码中实体的字段类型使用封装类型而不是基本类型
原因:基本类型是有默认值存在,而数据库中我们一旦设置字段可空,就会有NULL值存在。所以建议全部使用封装类型。下面附上各种基本类型的默认值


4、 分页算法
原因:分页推荐使用PageHelper,是利用Mybatis的底层插件机制修改Sql语句,也是无侵入。


更多说明

更多说明,可以参考github上的wiki页面

06-26 08:29