本期的译见,将带您继续前行:详细介绍一个完整的基于 JPA 的用户存储库实现,一个 JPA 的支撑模型和一些测试用例。
Misha&Boring 译 道客船长
在上期的《译见|构建用户管理微服务(二):实现领域模型》中,有着相当数量的涉及到实现领域模型的编码,它们构成了用户注册过程中所需要的全部逻辑。在第三部分中,作者将带你继续前行:详细介绍一个完整的基于 JPA 的用户存储库实现,一个 JPA 的支撑模型和一些测试用例。
使用 XML 来映射简单的 JAVA 对象
仅看到用户存储库,也许你就能想到在对它添加基于 JPA 的实现时会遇到什么困难。
public interface UserRepository { void delete(Long userId) throws NoSuchUserException; Optional<User> findById(Long id); Optional<User> findByEmail(String email); Optional<User> findByScreenName(String screenName); User save(User user); }
但是, 正如我在第一部分提到的, 我们将使用 DDD (域驱动设计), 因此, 在模型中就不能使用特定框架的依赖关系云 (包括 JPA 的注解) ,剩下的唯一可行性方法是用 XML 进行映射。如果我没有记错的话,自2010年以来,我再也没有接触过任何一个 orm.xml 的文件 , 这也就是我为什么开始怀念它的原因。
接下来我们看看XML文件中User的映射情况,以下是 user-orm.xml 的部分摘录。
<entity class="com.springuni.auth.domain.model.user.User" cacheable="true" metadata-complete="true"> <table name="user_"/> <named-query name="findByIdQuery"> <query> <![CDATA[ select u from User u where u.id = :userId and u.deleted = false ]]> </query> </named-query> <named-query name="findByEmailQuery"> <query> <![CDATA[ select u from User u where u.contactData.email = :email and u.deleted = false ]]> </query> </named-query> <named-query name="findByScreenNameQuery"> <query> <![CDATA[ select u from User u where u.screenName = :screenName and u.deleted = false ]]> </query> </named-query> <entity-listeners> <entity-listener class="com.springuni.commons.jpa.IdentityGeneratorListener"/> </entity-listeners> <attributes> <id name="id"/> <basic name="timezone"> <enumerated>STRING</enumerated> </basic> <basic name="locale"/> <basic name="confirmed"/> <basic name="locked"/> <basic name="deleted"/> <one-to-many name="confirmationTokens" fetch="LAZY" mapped-by="owner" orphan-removal="true"> <cascade> <cascade-persist/> <cascade-merge/> </cascade> </one-to-many> <element-collection name="authorities"> <collection-table name="authority"> <join-column name="user_id"/> </collection-table> </element-collection> <embedded name="auditData"/> <embedded name="contactData"/> <embedded name="password"/> <!-- Do not map email directly through its getter/setter --> <transient name="email"/> </attributes> </entity>
域驱动设计是一种持久化无关的方法,因此坚持设计一个没有具体目标数据结构的模型可能很有挑战性。当然, 它也存在优势, 即可对现实世界中的问题直接进行建模, 而不存在只能以某种方式使用某种技术栈之类的副作用。
public class User implements Entity<Long, User> { private Long id; private String screenName; ... private Set<String> authorities = new LinkedHashSet<>(); }
一般来说,一组简单的字符串或枚举值就能对用户的权限(或特权)进行建模了。
使用像 MongoDB 这样的文档数据库能够轻松自然地维护这个模型,如下所示。(顺便一提, 我还计划在本系列的后续内容中添加一个基于 Mongo 的存储库实现)
{ "id":123456789, "screenName":"test", ... "authorities":[ "USER", "ADMIN" ] }
然而, 在关系模型中, 权限的概念必须作为用户的子关系进行处理。但是在现实世界中, 这仅仅只是一套权限规则。我们需要如何弥合这样的差距呢?
在 JPA 2.0 中可以引入 ElementCollection 来进行操作,它的用法类似于 OneToMany。在这种情况下, 已经配置好的 JPA 提供的程序 (Hibernate) 将自动生成必要的子关系。
alter table authority add constraint FKoia3663r5o44m6knaplucgsxn foreign key (user_id) references user_
项目中的新模块
我一直在讨论的 springuni-auth-user-jpa 包含了一个完整的基于 JPA 的 UserRepository 实现。其目标是, 每个模块都应该只拥有那些对它们的操作来说绝对必要的依赖关系,而这些关系只需要依赖 JPA API 便可以实现。
springuni-commons-jpa 是一个支撑模块, 它能够使用预先配置好的 HikariCP 和 Hibernate 的组合作为实体管理器, 而不必关心其他细节。 它的特色是 AbstractJpaConfiguration, 类似于 Spring Boot 的 HibernateJpaAutoConfiguration。
然而我没有使用后者的原因是 Spring Boot 的自动配置需要一定的初始化。因为谷歌应用引擎标准环境是我的目标平台之一,因此能否快速地启动是至关重要的。
单元测试存储库
虽然有人可能会说, 对于存储库没必要进行过多的测试, 尤其是在使用 Spring Data 的 存储库接口的时候。但是我认为测试代码可以避免运行时存在的一些问题,例如错误的实体映射或错误的 JPQL 查询。
@RunWith(SpringJUnit4ClassRunner) @ContextConfiguration(classes = [UserJpaTestConfiguration]) @Transactional @Rollbackclass UserJpaRepositoryTest { @Autowired UserRepository userRepository User user @Before void before() { user = new User(1, "test", "test@springuni.com") user.addConfirmationToken(ConfirmationTokenType.EMAIL, 10) userRepository.save(user) } ... @Test void testFindById() { Optional<User> userOptional = userRepository.findById(user.id) assertTrue(userOptional.isPresent()) } ... }
这个测试用例启动了一个具有嵌入式 H2 数据库的实体管理器。H2 非常适合于测试, 因为它支持许多众所周知的数据库 (如 MySQL) 的兼容模式,可以模拟你的真实数据库。
下期预告:构建用户管理微服务(四):实现 REST 控制器
原文链接:https://www.springuni.com/user-management-microservice-part-3
未经允许不得转载:DaoCloud道客博客 » 译见|构建用户管理微服务(三):实现和测试存储库