I think this may be a case where I know the answer but just don't like it.
My starting point was an earlier question, Hibernate @Entity conflict with Spring @Autowired for non-column object.
I have an @Entity which is "naturally" linked in a one-to-many relationship with another set of entities. In my example, I'm calling it an ItemEntity, and it has a (very) large price history. So large, that having Hibernate lazy-load is a performance killer because real use cases never need all of the history (hundreds of thousands of prices, vs the few hundred typically needed). So I have a PriceCache service that gets what I need on-demand.
The "natural" thing, from a normal use case, is to retrieve the ItemEntity of interest, then ask for the associated price history during some time range. As was argued in the above post, having that service inside ItemEntity is not normal, though it can be done and made to work.
In the sample code below, I've written this in a different way, by having an Item interface, with an implementation that is effectively a proxy for ItemEntity plus the PriceCache service. This is example code, and bits are missing; I think (hope) there's enough present to be clear.
My set of entities and their properties is not so large that I couldn't do this by hand for all of them; a couple dozen entities, each with 5-20 properties. That would be moderately painful and boring, but it should work.
But...is there an easier way to create what is essentially a proxy object with an extra service injected? Or maybe the question is, is there a lazier way to do this?
@Entity @Table(name="item")
public class ItemEntity {
@Id @Column(name="id")
private long id;
@Column(name="name")
private String name;
/* ... setters, getters ... */
}
@Service
public class ItemCache {
@Autowired
private ItemDAO itemDAO;
@Autowired
private PriceCache priceCache;
private Map<Long,Item> itemCache;
public ItemCache() {
itemCache = new HashMap<>();
}
public Item get(long id) {
if (itemCache.containsKey(id))
return itemCache.get(id);
ItemEntity itemEntity = itemDAO.find(id);
Item item = (itemEntity == null) ? null : new ItemImpl(itemEntity, priceCache);
itemCache.put(id, item); // caches nulls to avoid retry
return item;
}
}
@Service
public class PriceCache {
@Autowired
private PriceDAO priceDAO;
/* ... various cache/map structures to hold previous query results ... */
public PriceCache() {
/* ... initialize all those cache/map structures ... */
}
public Collection<Price> getPrices(long id, LocalDateTime begTime, LocalDateTime endTime) {
Collection<Price> results;
/* ... check the caches to see if we already have the data ... */
/* ... otherwise, use priceDAO to find it and save the results in the cache ... */
return results;
}
}
public interface Item {
public long getId();
public String getName();
public Collection<Price> getPrices(LocalDateTime begTime, LocalDateTime endTime);
}
public class ItemImpl implements Item {
public ItemImpl(ItemEntity underlying, PriceCache priceCache) { ... }
public long getId() {
return underlying.getId();
}
public String getName() {
return underlying.getName();
}
public Collection<Price> getPrices(LocalDateTime begTime, LocalDateTime endTime) {
priceCache.get(getId(), begTime, endTime);
}
}