Spring Data JDBC通用DAO实现–迄今为止最轻量的ORM

我很高兴宣布Spring Data JDBC存储库项目的第一个版本。 这个开放源代码库的目的是为基于Spring框架中 JdbcTemplate关系数据库提供通用,轻量且易于使用的DAO实现,与项目的Spring Data 框架兼容。

设计目标

  • 轻巧,快速且开销低。 只有少数几个类, 没有XML,注释,反射
  • 这不是成熟的ORM 。 没有关系处理,延迟加载,脏检查,缓存
  • 在几秒钟内实现CRUD
  • 对于JPA过大的小型应用程序
  • 在需要简单性或考虑将来迁移到JPA时使用
  • 对数据库方言差异的最小化支持(例如,透明的结果分页)

特征

每个DAO为以下内容提供内置支持:

  • 通过RowMapper抽象到域对象/从域对象映射
  • 生成的和用户定义的主键
  • 提取生成的密钥
  • 复合(多列)主键
  • 不变的领域对象
  • 分页(请求结果子集)
  • 按几列排序(与数据库无关)
  • 多对一关系的可选支持
  • 支持的数据库(连续测试):
    • 的MySQL
  • 通过SqlGenerator类可以轻松扩展到其他数据库方言。
  • 通过ID轻松检索记录

API

与Spring Data PagingAndSortingRepository抽象兼容, 所有这些方法都为您实现

public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
             T  save(T entity);
    Iterable<T> save(Iterable<? extends T> entities);
             T  findOne(ID id);
        boolean exists(ID id);
    Iterable<T> findAll();
           long count();
           void delete(ID id);
           void delete(T entity);
           void delete(Iterable<? extends T> entities);
           void deleteAll();
    Iterable<T> findAll(Sort sort);
        Page<T> findAll(Pageable pageable);
}

还完全支持PageableSort参数,这意味着您可以通过任意属性免费获得分页和排序 。 例如,假设您有userRepository扩展了PagingAndSortingRepository<User, String>接口(由库为您实现),并且在应用某种排序后,您请求了USERS表的第5页,每页10个:

Page<User> page = userRepository.findAll(
    new PageRequest(
        5, 10,
        new Sort(
            new Order(DESC, "reputation"),
            new Order(ASC, "user_name")
        )
    )
);

Spring Data JDBC存储库库会将此调用转换为(PostgreSQL语法):

SELECT *
FROM USERS
ORDER BY reputation DESC, user_name ASC
LIMIT 50 OFFSET 10

…甚至(Derby语法):

SELECT * FROM (
    SELECT ROW_NUMBER() OVER () AS ROW_NUM, t.*
    FROM (
        SELECT *
        FROM USERS
        ORDER BY reputation DESC, user_name ASC
        ) AS t
    ) AS a
WHERE ROW_NUM BETWEEN 51 AND 60

无论使用哪个数据库,都将获得Page<User>对象作为回报(您仍然必须自己提供RowMapper<User>才能将其从ResultSet转换为域对象。如果您还不了解Spring Data项目,则Page<T>是一个很棒的抽象,不仅封装了List<User> ,而且还提供了元数据,例如记录总数,我们当前所在的页面等。

使用理由

入门

有关更多示例和工作代码,请不要忘记检查项目测试

先决条件

Maven坐标:

<dependency>
    <groupId>com.blogspot.nurkiewicz</groupId>
    <artifactId>jdbcrepository</artifactId>
    <version>0.1</version>
</dependency>

不幸的是,该项目尚未在Maven中央存储库中 。 目前,您可以通过克隆将库安装在本地存储库中:

$ git clone git://github.com/nurkiewicz/spring-data-jdbc-repository.git
$ git checkout 0.1
$ mvn javadoc:jar source:jar install

为了启动您的项目,必须存在DataSource bean并启用事务管理。 这是最小的MySQL配置:

@EnableTransactionManagement
@Configuration
public class MinimalConfig {
 
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }
 
    @Bean
    public DataSource dataSource() {
        MysqlConnectionPoolDataSource ds = new MysqlConnectionPoolDataSource();
        ds.setUser("user");
        ds.setPassword("secret");
        ds.setDatabaseName("db_name");
        return ds;
    }
 
}
具有自动生成的密钥的实体

假设您有一个具有自动生成的密钥(MySQL语法)的数据库表:

CREATE TABLE COMMENTS (
    id INT AUTO_INCREMENT,
    user_name varchar(256),
    contents varchar(1000),
    created_time TIMESTAMP NOT NULL,
    PRIMARY KEY (id)
);

首先,您需要创建到该表的域对象User映射(就像在任何其他ORM中一样):

public class Comment implements Persistable<Integer> {
 
    private Integer id;
    private String userName;
    private String contents;
    private Date createdTime;
 
    @Override
    public Integer getId() {
        return id;
    }
 
    @Override
    public boolean isNew() {
        return id == null;
    }
 
    //getters/setters/constructors/...
}

除了标准的Java样板之外,您还应该注意实现Persistable<Integer> ,其中Integer是主键的类型。 Persistable<T>是一个来自Spring Data项目的接口,这是我们对您的域对象的唯一要求。

最后,我们准备创建CommentRepository DAO:

@Repository
public class CommentRepository extends JdbcRepository<Comment, Integer> {
 
    public CommentRepository() {
        super(ROW_MAPPER, ROW_UNMAPPER, "COMMENTS");
    }
 
    public static final RowMapper<Comment> ROW_MAPPER = //see below
 
    private static final RowUnmapper<Comment> ROW_UNMAPPER = //see below
 
    @Override
    protected Comment postCreate(Comment entity, Number generatedId) {
        entity.setId(generatedId.intValue());
        return entity;
    }
}

首先,我们使用@Repository批注标记DAO bean。 它启用持久性异常转换。 通过CLASSPATH扫描也可以发现此类带注释的bean。

如您所见,我们扩展了JdbcRepository<Comment, Integer> ,它是该库的中心类,提供了所有PagingAndSortingRepository方法的实现。 它的构造函数具有三个必需的依赖项: RowMapperRowUnmapper和表名。 您也可以提供ID列名,否则使用默认的"id"

如果您曾经使用过Spring的JdbcTemplate ,则应该熟悉RowMapper界面。 我们需要以某种方式将ResultSet列提取到一个对象中。 毕竟,我们不想使用原始的JDBC结果。 这很简单:

public static final RowMapper<Comment> ROW_MAPPER = new RowMapper<Comment>() {
 
    @Override
    public Comment mapRow(ResultSet rs, int rowNum) throws SQLException {
        return new Comment(
                rs.getInt("id"),
                rs.getString("user_name"),
                rs.getString("contents"),
                rs.getTimestamp("created_time")
        );
    }
};

RowUnmapper来自此库,它本质上与RowMapper相反:接收一个对象并将其转换为Map 。 库稍后使用此映射来构造SQL CREATE / UPDATE查询:

private static final RowUnmapper<Comment> ROW_UNMAPPER = new RowUnmapper<Comment>() {
    @Override
    public Map<String, Object> mapColumns(Comment comment) {
        Map<String, Object> mapping = new LinkedHashMap<String, Object>();
        mapping.put("id", comment.getId());
        mapping.put("user_name", comment.getUserName());
        mapping.put("contents", comment.getContents());
        mapping.put("created_time", new java.sql.Timestamp(comment.getCreatedTime().getTime()));
        return mapping;
    }
};

如果您从不更新数据库表(仅读取插入在其他位置的一些参考数据),则可以跳过RowUnmapper参数或使用MissingRowUnmapper

最后一个难题是postCreate()回调方法,该方法在插入对象后调用。 您可以使用它来检索生成的主键并更新域对象(如果域对象是不可变的,则返回新的主键)。 如果不需要它,就不要重写postCreate() 。 根据此示例,检查JdbcRepositoryGeneratedKeyTest以获取有效的代码。

到目前为止,您可能会觉得与JPA或Hibernate相比,有很多手工工作。 但是,众所周知,各种JPA实现和其他ORM框架都会引入大量开销并显示一些学习曲线。 这个微小的库有意让用户承担一些责任,以避免复杂的映射,反射,注释……并非总是需要的所有隐式性。 该项目无意替代成熟稳定的ORM框架。 相反,它试图填补原始JDBC和ORM之间的利基,其中简单性和低开销是关键特征。

具有手动分配的密钥的实体

在此示例中,我们将看到如何处理具有用户定义的主键的实体。 让我们从数据库模型开始:

CREATE TABLE USERS (
    user_name varchar(255),
    date_of_birth TIMESTAMP NOT NULL,
    enabled BIT(1) NOT NULL,
    PRIMARY KEY (user_name)
);

…和User域模型:

public class User implements Persistable<String> {
 
    private transient boolean persisted;
 
    private String userName;
    private Date dateOfBirth;
    private boolean enabled;
 
    @Override
    public String getId() {
        return userName;
    }
 
    @Override
    public boolean isNew() {
        return !persisted;
    }
 
    public User withPersisted(boolean persisted) {
        this.persisted = persisted;
        return this;
    }
 
    //getters/setters/constructors/...
 
}

注意,添加了特殊的persisted瞬态标志。 来自Spring Data项目的CrudRepository.save()合同要求一个实体知道它是否已经保存( isNew() )方法–没有单独的create()update()方法。 对于自动生成的键(参见上面的Comment ),实现isNew()很简单,但是在这种情况下,我们需要一个额外的瞬态字段。 如果您讨厌这种解决方法,并且只插入数据而从不更新,则始终可以从isNew()返回true

最后是我们的DAO, UserRepository bean:

@Repository
public class UserRepository extends JdbcRepository<User, String> {
 
    public UserRepository() {
        super(ROW_MAPPER, ROW_UNMAPPER, "USERS", "user_name");
    }
 
    public static final RowMapper<User> ROW_MAPPER = //...
 
    public static final RowUnmapper<User> ROW_UNMAPPER = //...
 
    @Override
    protected User postUpdate(User entity) {
        return entity.withPersisted(true);
    }
 
    @Override
    protected User postCreate(User entity, Number generatedId) {
        return entity.withPersisted(true);
    }
}

"USERS""user_name"参数指定表名称和主键列名称。 我将保留mapper和unmapper的详细信息(请参阅源代码 )。 但是请注意postUpdate()postCreate()方法。 它们确保一旦对象被持久保存,就设置了persisted标志,以便随后对save()调用将更新现有实体,而不是尝试重新插入它。

根据此示例,检查JdbcRepositoryManualKeyTest以获得有效的代码。

复合主键

我们还支持复合主键(由几列组成的主键)。 以该表为例:

CREATE TABLE BOARDING_PASS (
    flight_no VARCHAR(8) NOT NULL,
    seq_no INT NOT NULL,
    passenger VARCHAR(1000),
    seat CHAR(3),
    PRIMARY KEY (flight_no, seq_no)
);

我希望您注意到Peristable<T>的主键类型:

public class BoardingPass implements Persistable<Object[]> {
 
    private transient boolean persisted;
 
    private String flightNo;
    private int seqNo;
    private String passenger;
    private String seat;
 
    @Override
    public Object[] getId() {
        return pk(flightNo, seqNo);
    }
 
    @Override
    public boolean isNew() {
        return !persisted;
    }
 
    //getters/setters/constructors/...
 
}

不幸的是,我们不支持将所有ID值封装在一个对象中的小数值类(就像JPA使用@IdClass ),因此您必须使用Object[]数组。 定义DAO类类似于我们已经看到的内容:

public class BoardingPassRepository extends JdbcRepository<BoardingPass, Object[]> {
    public BoardingPassRepository() {
        this("BOARDING_PASS");
    }
 
    public BoardingPassRepository(String tableName) {
        super(MAPPER, UNMAPPER, new TableDescription(tableName, null, "flight_no", "seq_no")
        );
    }
 
    public static final RowMapper<BoardingPass> ROW_MAPPER = //...
 
    public static final RowUnmapper<BoardingPass> UNMAPPER = //...
 
}

需要注意的两件事:我们扩展了JdbcRepository<BoardingPass, Object[]>并且按预期提供了两个ID列名称: "flight_no", "seq_no" 。 我们通过提供由Object[]包裹的flight_noseq_no (必须seq_no顺序)值来查询此类DAO:

BoardingPass pass = repository.findOne(new Object[] {"FOO-1022", 42});

毫无疑问,这在实践中很麻烦,因此我们提供了微小的辅助方法,您可以静态导入:

import static com.blogspot.nurkiewicz.jdbcrepository.JdbcRepository.pk;
//...
 
BoardingPass foundFlight = repository.findOne(pk("FOO-1022", 42));

根据此示例,检查JdbcRepositoryCompoundPkTest以获取工作代码。

交易次数

该库与事务管理完全正交。 每个存储库的每种方法都需要运行事务,具体取决于您进行设置。 通常,您将@Transactional放在服务层上(称为DAO bean)。 我不建议@Transactional放在每个DAO bean上

快取

Spring Data JDBC存储库库不提供任何缓存抽象或支持。 但是, 在Spring中使用缓存抽象@Cacheable层添加@Cacheable DAO或服务之上非常简单。 另请参见: Spring中的@Cacheable开销

会费

..总是欢迎。 不要犹豫, 提交错误报告提出请求 。 现在最大的缺失功能是对MSSQL和Oracle数据库的支持。 如果有人可以看一下,那就太好了。

测试中

该库已使用Travis( 建立状态 )。 测试套件包括265个测试 (53个不同的测试,每个测试针对5个不同的数据库运行:MySQL,PostgreSQL,H2,HSQLDB和Derby。

在填写错误报告或提交新功能时,请尝试包括支持测试用例。 每个拉取请求都会在单独的分支上自动进行测试。

建造

分叉后, 正式的存储库构建就像运行一样简单:

$ mvn install

您将在JUnit测试执行过程中注意到大量异常。 这个是正常的。 一些测试是针对仅在Travis CI服务器上可用的MySQL和PostgreSQL运行的。 当这些数据库服务器不可用时,只需跳过整个测试:
结果:

Tests run: 265, Failures: 0, Errors: 0, Skipped: 106

异常堆栈跟踪来自根AbstractIntegrationTest

设计

库仅包含少数几个类,如下图所示:

UML图

JdbcRepository是实现所有PagingAndSortingRepository方法的最重要的类。 每个用户存储库都必须扩展此类。 同样,每个这样的存储库必须至少实现RowMapperRowUnmapper (仅当您要修改表数据时)。

SQL生成委托给SqlGeneratorPostgreSqlGenerator.DerbySqlGenerator用于与标准生成器不DerbySqlGenerator的数据库。

执照

该项目是在Apache许可的 2.0版下发布的 (与Spring框架相同)。

参考: NoBlogDefFound博客中JCG合作伙伴 Tomasz Nurkiewicz 为程序员提供的概率分布

翻译自: https://www.javacodegeeks.com/2013/01/spring-data-jdbc-generic-dao-implementation-most-lightweight-orm-ever.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值