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 wa开发者_JAVA百科s 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.
Another option I just found is inside the Dao that returns the list of ItemDto, before returning the list, I would execute this:
getEntityManager().clear();
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?
精彩评论