Spring + JPA: object does not get fetched from the

2019-05-31 23:53发布

问题:

I am implementing an online shop. I have the following code for saving an order:

@Service
@Transactional
public class OrderServiceImpl extends GenericServiceImpl<Order, Integer> implements OrderService {

    @Inject
    private ItemRepository itemRepository;

    @Override
    public void saveOrder(Order order) {
        this.updateItemsAccordingToOrderedQuantities(order);
        repository.save(order);
    }

    private void updateItemsAccordingToOrderedQuantities(Order order) {
        List<OrderedItem> orderedItems = order.getOrderedItems();
        for (OrderedItem orderedItem : orderedItems) {
            // fetch from database
            Item item = itemRepository.findOne(orderedItem.getItem().getId()); 

            item.reduceWeightInColdStoreBy(orderedItem.getWeight());
            itemRepository.update(item);
        }
    }
}

Before actually saving the order I update the "weight" property of each item (some quantity gets sold with this order, so there is less left).

OrderedItem objects hold the reference to Item, but I want to fetch the fresh Item from the database (to additionally check there is enough on sale, in case Item table in database changed and UI didn't update before user submitted order). findOne method I am calling for this purpose is implemented as follows:

@Repository
public class GenericRepositoryImpl<T, ID extends Serializable> implements GenericRepository<T, ID> {
    @PersistenceContext
    EntityManager em;

    protected Class<T> entityClass;

    public GenericRepositoryImpl(Class<T> entityClass) {
        this.entityClass = entityClass;
    }

    @Override
    public T findOne(ID id) {
        return em.find(entityClass, id);
    }
}

After the call item is not null, but it is not fetched from the database: I have EclipseLink logging turned on, at "FINEST" level, and there is no SELECT query.

I thought that it is because EclipseLink has this item in its persistence context, so it doesn't execute the query. So I've tried adding em.clear() before return in findOne method implementation, but it didn't help.

How do I actually get the fresh item from the database?

Thank you in advance.

P.S. I've tried querying explicitly in my findOne method, like so:

    @Override
    public T findOne(ID id) {
        T result = null;
        try {
            TypedQuery<T> query = em.createQuery("SELECT e FROM " + entityClass.getName() + " e", entityClass);
            result = query.getSingleResult();
        } catch (RuntimeException e) {
            e.printStackTrace();
        }
        return result;
    }

This way I get the exception: java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST: ftcApp.model.Order@cf616dce. I have no idea why, because I'm not doing persist.

回答1:

The find method will return the copy that exists in the persistence context rather than querying the database a second time. You can call the refresh method of the entity manager in order to ensure the copy in the persistence context has the latest values from the database: