【发布时间】:2016-12-26 22:09:40
【问题描述】:
基本上,在使用 Jax-RS 和 Spring 开发 REST 服务时,我试图了解如何编写正确(或“正确编写”?)事务代码。此外,我们使用 JOOQ 进行数据访问。但这应该不是很相关...
考虑一个简单的模型,我们有一些组织,它们有这些字段:"id", "name", "code"。所有这些都必须是唯一的。还有一个status 字段。
组织可能会在某个时候被删除。但我们不想完全删除数据,因为我们想将其保存用于分析/维护目的。所以我们只需将组织“状态”字段设置为'REMOVED'。
因为我们没有从表中删除组织行,所以我们不能简单地将唯一约束放在“名称”列上,因为我们可能会删除组织,然后创建一个具有相同名称的新组织。但是让我们假设代码必须是全局唯一的,所以我们对 code 列有唯一约束。
因此,让我们看看这个简单的示例,它创建了组织,并在此过程中执行了一些检查。
资源:
@Component
@Path("/api/organizations/{organizationId: [0-9]+}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaTypeEx.APPLICATION_JSON_UTF_8)
public class OrganizationResource {
@Autowired
private OrganizationService organizationService;
@Autowired
private DtoConverter dtoConverter;
@POST
public OrganizationResponse createOrganization(@Auth Person person, CreateOrganizationRequest request) {
if (organizationService.checkOrganizationWithNameExists(request.name())) {
// this throws special Exception which is intercepted and translated to response with 409 status code
throw Responses.abortConflict("organization.nameExist", ImmutableMap.of("name", request.name()));
}
if (organizationService.checkOrganizationWithCodeExists(request.code())) {
throw Responses.abortConflict("organization.codeExists", ImmutableMap.of("code", request.code()));
}
long organizationId = organizationService.create(person.user().id(), request.name(), request.code());
return dtoConverter.from(organization.findById(organizationId));
}
}
DAO 服务如下所示:
@Transactional(DBConstants.SOME_TRANSACTION_MANAGER)
public class OrganizationServiceImpl implements OrganizationService {
@Autowired
@Qualifier(DBConstants.SOME_DSL)
protected DSLContext context;
@Override
public long create(long userId, String name, String code) {
Organization organization = new Organization(null, userId, name, code, OrganizationStatus.ACTIVE);
OrganizationRecord organizationRecord = JooqUtil.insert(context, organization, ORGANIZATION);
return organizationRecord.getId();
}
@Override
public boolean checkOrganizationWithNameExists(String name) {
return checkOrganizationExists(Tables.ORGANIZATION.NAME, name);
}
@Override
public boolean checkOrganizationWithCodeExists(String code) {
return checkOrganizationExists(Tables.ORGANIZATION.CODE, code);
}
private boolean checkOrganizationExists(TableField<OrganizationRecord, String> checkField, String checkValue) {
return context.selectCount()
.from(Tables.ORGANIZATION)
.where(checkField.eq(checkValue))
.and(Tables.ORGANIZATION.ORGANIZATION_STATUS.ne(OrganizationStatus.REMOVED))
.fetchOne(DSL.count()) > 0;
}
}
这带来了一些问题:
- 我应该把
@Transactional注解放在Resource的createOrganization方法上吗?或者我应该再创建一个与 DAO 对话的服务并将 @Transactional 注释放到它的方法中?还有什么? - 如果两个用户同时发送具有相同
"code"字段的请求会发生什么情况。在提交第一个事务之前,检查已成功通过,因此不会发送 409 响应。比第一个事务将正确提交,但第二个事务将违反数据库约束。这将抛出 SQLException。如何优雅地处理它?我的意思是我仍然想在客户端显示漂亮的错误消息,说该名称已被使用。但我不能真正解析 SQLException 或 smth.. 我可以吗? - 与上一个类似,但这次“名称”不是唯一的。在这种情况下,第二笔交易不会违反任何约束,这会导致两个组织同名,这违反了我们的业务约束。
- 我在哪里可以看到/学习教程/代码/等,您会考虑如何编写具有复杂业务逻辑的正确/可靠的 REST+DB 代码的很好的示例。 Github/书籍/博客,随便。我自己试图找到类似的东西,但大多数示例只关注管道 - 将这些库添加到 maven,使用这些注释,最后就是你的简单 CRUD。它们根本不包含任何交易考虑。即
更新: 我知道隔离级别和通常的error/isolation matrix(脏读等)。我遇到的问题是找到一些“生产就绪”的样本来学习。或者是一本关于某个主题的好书。我仍然没有真正了解如何正确处理所有错误..我想我需要重试几次,如果事务失败..而不仅仅是抛出一些通用错误并实现客户端,它可以处理..但是做每当我使用范围查询时,我真的必须使用 SERIALIZABLE 模式吗?因为它会极大地影响性能。但否则我怎么能保证交易会失败..
无论如何,我已经决定,现在我需要更多时间来学习事务和数据库管理,以解决这个问题......
【问题讨论】:
标签: java spring rest transactions jax-rs