POJO DTO 的 JPA 同步/提交错误,即使我不想保存它
由于缺乏关键词来描述这个场景,让我继续描述它。课程已被简化。
鉴于此:
public ItemController {
@Autowired
ItemDtoService ItemDtoService;
@Autowired
DiscountService discountService;
@RequestMapping(value = "/viewItems", method = RequestMethod.POST)
public void process() {
List<ItemDto> ItemDtos = ItemDtoService.getItemDtos();
for(ItemDto i: ItemDtos) {
boolean isDiscounted = discountService.hasDiscount(i); //throws exception here on iteration 2 and the last iteration, ItemDto was discounted
if (isDiscounted) {
i.setPrice(discountService.getDiscountedPrice(i));
//do some other i.setter, basically modify the pojo
}
}
}
}
在以下情况下,discountService.hasDiscount 会引发异常:
- 在后续迭代
- 和上一次迭代中,ItemDto 已打折。
例外是:
Caused by: org.hibernate.exception.SQLGrammarException: could not update: [somepackage.ItemDto#364]
在堆栈跟踪中的某个地方,您将看到以下内容:
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:456)"
问题是方法调用使用了下面的 dao 方法,即 @Transactional (也许有一个很好的理由,即使它只是一个查询,复杂的查询)。当 JPA Tx 管理器在方法调用结束时完成其工作时,它会看到 pojo 已修改并尝试同步它。 ItemDto pojo 确实有 @Entity,因为在 ItemDtoService.getItemDtos 内部使用 getEntityManager().createNativeQuery(nativeSql, ItemDto.class)。其他 5 个类的详细信息在这里:
@Entity
public class ItemDto{
//body
}
@Service
public class ItemService {
@Autowired
ItemDao itemDao;
public List<ItemDto> getItems() {
return itemDao.getItems(); //for sake of simplicity
}
}
@Repository
@Transactional
public class ItemDaoImpl {
public List<ItemDto> getItems() {
String nativeSql = "select...."
return getEntityManager().createNativeQuery(nativeSql, ItemDto.class);
}
}
@Service
public class DiscountService {
@Autowired
DiscountDao discountDao;
public boolean hasDiscount(ItemDto i) {
boolean hasDiscount = discountDao.hasDiscount(i);
//do other service stuff that might influence the hasDiscount flag
return hasDiscount;
}
}
@Repository
@Transactional
public class DiscountDaoImpl {
public boolean hasDiscount(ItemDto i) {
String nativeSql = "select...."
boolean hasDiscount;
//in reality the query is a complicated joins, executes and returns if has discount or not
return hasDiscount;
}
}
我做错了什么?
我尝试和工作过的一些选项包括:
- 在 Dao 方法上添加到 @Transactional (readonly=true) 因为它们只是查询(尽管负面影响可能是 由于复杂的查询而有意进行事务处理,并且可能需要 锁定以防止脏读)
- 在Controller中,创建一个单独的循环进行修改,它 然后有 2 个循环,1 个循环遍历项目并查看哪个是 打折后,将这些信息存储在某处以供第二日稍后参考 循环,它对所说的 pojos 进行修改,
我正在查看其他选项,如果您发现其编码方式有问题,请发表评论。
Due to lack of key words to capture this scenario, let me just proceed to describe it. The classes have been simplified.
Given this:
public ItemController {
@Autowired
ItemDtoService ItemDtoService;
@Autowired
DiscountService discountService;
@RequestMapping(value = "/viewItems", method = RequestMethod.POST)
public void process() {
List<ItemDto> ItemDtos = ItemDtoService.getItemDtos();
for(ItemDto i: ItemDtos) {
boolean isDiscounted = discountService.hasDiscount(i); //throws exception here on iteration 2 and the last iteration, ItemDto was discounted
if (isDiscounted) {
i.setPrice(discountService.getDiscountedPrice(i));
//do some other i.setter, basically modify the pojo
}
}
}
}
An exception is thrown at the discountService.hasDiscount when:
- on subsequent iteration
- and the previous iteration, the ItemDto was discounted.
Exception is:
Caused by: org.hibernate.exception.SQLGrammarException: could not update: [somepackage.ItemDto#364]
And somewhere in the stacktrace you will see this:
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:456)"
The problem is that method call uses a dao method underneath that is @Transactional (and maybe for a good reason even though it's only a query, complicated query). When the JPA Tx manager does its job upon method call end, it sees the pojo as modified and tries to synch it. The ItemDto pojo does have @Entity because inside ItemDtoService.getItemDtos uses the getEntityManager().createNativeQuery(nativeSql, ItemDto.class). The 5 other class details are here:
@Entity
public class ItemDto{
//body
}
@Service
public class ItemService {
@Autowired
ItemDao itemDao;
public List<ItemDto> getItems() {
return itemDao.getItems(); //for sake of simplicity
}
}
@Repository
@Transactional
public class ItemDaoImpl {
public List<ItemDto> getItems() {
String nativeSql = "select...."
return getEntityManager().createNativeQuery(nativeSql, ItemDto.class);
}
}
@Service
public class DiscountService {
@Autowired
DiscountDao discountDao;
public boolean hasDiscount(ItemDto i) {
boolean hasDiscount = discountDao.hasDiscount(i);
//do other service stuff that might influence the hasDiscount flag
return hasDiscount;
}
}
@Repository
@Transactional
public class DiscountDaoImpl {
public boolean hasDiscount(ItemDto i) {
String nativeSql = "select...."
boolean hasDiscount;
//in reality the query is a complicated joins, executes and returns if has discount or not
return hasDiscount;
}
}
What am I doing wrong?
Some of the options I tried and worked include:
- add to the @Transactional the (readonly=true) on the Dao methods
since they are only queries (negative effect though is those might
be intentionally transactional due to complex queries, and may need
locking to prevent dirty reads) - in the Controller, create a separate loop for modification, it
then have 2 loops, 1 for looping through items and seeing which is
discounted, store those info somewhere to be referenced later on 2nd
loop, which does the modification of said pojos
I am looking at other options, and please comment if you see something wrong with the way it was coded.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我刚刚找到的另一个选项是在返回 ItemDto 列表的 Dao 内部,在返回列表之前,我会执行以下操作:
它工作正常,因为列表无论如何都是 Dto 并且人们会期望这些不需要数据库同步,同时@Transactional 被保留以进行必要的锁定以实现一致的读取。
这是另一种选择,但真正最合适的方法是什么?
Another option I just found is inside the Dao that returns the list of ItemDto, before returning the list, I would execute this:
It works fine because the list is Dto anyways and one would expect that these require no DB synching, at the same time the @Transactional is retained for necessary locking for consistent reads.
That's one more alternative, but what is the most appropriate way really?