阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

设计ORM

29次阅读
没有评论

共计 5105 个字符,预计需要花费 13 分钟才能阅读完成。

我们从前几节可以看到,所谓 ORM,也是建立在 JDBC 的基础上,通过 ResultSet 到 JavaBean 的映射,实现各种查询。有自动跟踪 Entity 修改的全自动化 ORM 如 Hibernate 和 JPA,需要为每个 Entity 创建代理,也有完全自己映射,连 INSERT 和 UPDATE 语句都需要手动编写的 MyBatis,但没有任何透明的 Proxy。

而查询是涉及到数据库使用最广泛的操作,需要最大的灵活性。各种 ORM 解决方案各不相同,Hibernate 和 JPA 自己实现了 HQL 和 JPQL 查询语法,用以生成最终的 SQL,而 MyBatis 则完全手写,每增加一个查询都需要先编写 SQL 并增加接口方法。

还有一种 Hibernate 和 JPA 支持的 Criteria 查询,用 Hibernate 写出来类似:

DetachedCriteria criteria = DetachedCriteria.forClass(User.class);
criteria.add(Restrictions.eq("email", email))
        .add(Restrictions.eq("password", password));
List<User> list = (List<User>) hibernateTemplate.findByCriteria(criteria);

上述 Criteria 查询写法复杂,但和 JPA 相比,还是小巫见大巫了:

var cb = em.getCriteriaBuilder();
CriteriaQuery<User> q = cb.createQuery(User.class);
Root<User> r = q.from(User.class);
q.where(cb.equal(r.get("email"), cb.parameter(String.class, "e")));
TypedQuery<User> query = em.createQuery(q);
query.setParameter("e", email);
List<User> list = query.getResultList();

此外,是否支持自动读取一对多和多对一关系也是全自动化 ORM 框架的一个重要功能。

如果我们自己来设计并实现一个 ORM,应该吸取这些 ORM 的哪些特色,然后高效实现呢?

设计 ORM 接口

任何设计,都必须明确设计目标。这里我们准备实现的 ORM 并不想要全自动 ORM 那种自动读取一对多和多对一关系的功能,也不想给 Entity 加上复杂的状态,因此,对于 Entity 来说,它就是纯粹的 JavaBean,没有任何 Proxy。

此外,ORM 要兼顾易用性和适用性。易用性是指能覆盖 95% 的应用场景,但总有一些复杂的 SQL,很难用 ORM 去自动生成,因此,也要给出原生的 JDBC 接口,能支持 5% 的特殊需求。

最后,我们希望设计的接口要易于编写,并使用流式 API 便于阅读。为了配合编译器检查,还应该支持泛型,避免强制转型。

以 User 类为例,我们设计的查询接口如下:

// 按主键查询: SELECT * FROM users WHERE id = ?
User u = db.get(User.class, 123);

// 条件查询唯一记录: SELECT * FROM users WHERE email = ? AND password = ?
User u = db.from(User.class)
           .where("email=? AND password=?", "[email protected]", "bob123")
           .unique();

// 条件查询多条记录: SELECT * FROM users WHERE id < ? ORDER BY email LIMIT ?, ?
List<User> us = db.from(User.class)
                  .where("id < ?", 1000)
                  .orderBy("email")
                  .limit(0, 10)
                  .list();

// 查询特定列: SELECT id, name FROM users WHERE email = ?
User u = db.select("id", "name")
           .from(User.class)
           .where("email = ?", "[email protected]")
           .unique();

这样的流式 API 便于阅读,也非常容易推导出最终生成的 SQL。

对于插入、更新和删除操作,就相对比较简单:

// 插入 User:
db.insert(user);

// 按主键更新更新 User:
db.update(user);

// 按主键删除 User:
db.delete(User.class, 123);

对于 Entity 来说,通常一个表对应一个。手动列出所有 Entity 是非常麻烦的,一定要传入 package 自动扫描。

最后,ORM 总是需要元数据才能知道如何映射。我们不想编写复杂的 XML 配置,也没必要自己去定义一套规则,直接使用 JPA 的注解就行。

实现 ORM

我们并不需要从 JDBC 底层开始编写,并且,还要考虑到事务,最好能直接使用 Spring 的声明式事务。实际上,我们可以设计一个全局 DbTemplate,它注入了 Spring 的JdbcTemplate,涉及到数据库操作时,全部通过JdbcTemplate 完成,自然天生支持 Spring 的声明式事务,因为这个 ORM 只是在 JdbcTemplate 的基础上做了一层封装。

AppConfig 中,我们初始化所有 Bean 如下:

@Configuration
@ComponentScan
@EnableTransactionManagement
@PropertySource("jdbc.properties")
public class AppConfig {@Bean
    DataSource createDataSource() {...}

    @Bean
    JdbcTemplate createJdbcTemplate(@Autowired DataSource dataSource) {return new JdbcTemplate(dataSource);
    }

    @Bean
    DbTemplate createDbTemplate(@Autowired JdbcTemplate jdbcTemplate) {return new DbTemplate(jdbcTemplate, "com.itranswarp.learnjava.entity");
    }

    @Bean
    PlatformTransactionManager createTxManager(@Autowired DataSource dataSource) {return new DataSourceTransactionManager(dataSource);
    }
}

以上就是我们所需的所有配置。

编写业务逻辑,例如UserService,写出来像这样:

@Component
@Transactional
public class UserService {@Autowired
    DbTemplate db;

    public User getUserById(long id) {return db.get(User.class, id);
    }

    public User getUserByEmail(String email) {return db.from(User.class)
                 .where("email = ?", email)
                 .unique();}

    public List<User> getUsers(int pageIndex) {int pageSize = 100;
        return db.from(User.class)
                 .orderBy("id")
                 .limit((pageIndex - 1) * pageSize, pageSize)
                 .list();}

    public User register(String email, String password, String name) {User user = new User();
        user.setEmail(email);
        user.setPassword(password);
        user.setName(name);
        user.setCreatedAt(System.currentTimeMillis());
        db.insert(user);
        return user;
    }
    ...
}

上述代码给出了 ORM 的接口,以及如何在业务逻辑中使用 ORM。下一步,就是如何实现这个DbTemplate。这里我们只给出框架代码,有兴趣的童鞋可以自己实现核心代码:

public class DbTemplate {private JdbcTemplate jdbcTemplate;

    // 保存 Entity Class 到 Mapper 的映射:
    private Map<Class<?>, Mapper<?>> classMapping;

    public <T> T fetch(Class<T> clazz, Object id) {Mapper<T> mapper = getMapper(clazz);
        List<T> list = (List<T>) jdbcTemplate.query(mapper.selectSQL, new Object[] { id}, mapper.rowMapper);
        if (list.isEmpty()) {return null;
        }
        return list.get(0);
    }

    public <T> T get(Class<T> clazz, Object id) {...}

    public <T> void insert(T bean) {...}

    public <T> void update(T bean) {...}

    public <T> void delete(Class<T> clazz, Object id) {...}
}

实现链式 API 的核心代码是第一步从 DbTemplate 调用 select()from()时实例化一个 CriteriaQuery 实例,并在后续的链式调用中设置它的字段:

public class DbTemplate {
    ...
    public Select select(String... selectFields) {return new Select(new Criteria(this), selectFields);
    }

    public <T> From<T> from(Class<T> entityClass) {Mapper<T> mapper = getMapper(entityClass);
        return new From<>(new Criteria<>(this), mapper);
    }
}

然后以此定义 SelectFromWhereOrderByLimit 等。在 From 中可以设置 Class 类型、表名等:

public final class From<T> extends CriteriaQuery<T> {From(Criteria<T> criteria, Mapper<T> mapper) {super(criteria);
        // from 可以设置 class、tableName:
        this.criteria.mapper = mapper;
        this.criteria.clazz = mapper.entityClass;
        this.criteria.table = mapper.tableName;
    }

    public Where<T> where(String clause, Object... args) {return new Where<>(this.criteria, clause, args);
    }
}

Where 中可以设置条件参数:

public final class Where<T> extends CriteriaQuery<T> {Where(Criteria<T> criteria, String clause, Object... params) {super(criteria);
        this.criteria.where = clause;
        this.criteria.whereParams = new ArrayList<>();
        // add:
        for (Object param : params) {this.criteria.whereParams.add(param);
        }
    }
}

最后,链式调用的尽头是调用 list() 返回一组结果,调用 unique() 返回唯一结果,调用 first() 返回首个结果。

在 IDE 中,可以非常方便地实现链式调用:

设计 ORM

需要复杂查询的时候,总是可以使用 JdbcTemplate 执行任意复杂的 SQL。

练习

设计并实现一个微型 ORM。

下载练习

小结

ORM 框架就是自动映射数据库表结构到 JavaBean 的工具,设计并实现一个简单高效的 ORM 框架并不困难。

正文完
星哥说事-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-08-05发表,共计5105字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中