I'm using Morphia for MongoDB with Stripes Framework.
Let us assume I have two entities, Car (which describes a specific car, say some particular 1984 Honda Accord) and CarType (which specifies all Honda Accords of that kind):
The most natural way to model this seems:
#Entity
class Car {
#Id private String id; // VIN
private Date purchaseDate;
private Color color;
#Reference private CarType type;
// ..
}
#Entity
class CarType {
#Id private String id;
private String manufacturerId;
private float engineDisplacement;
// ..
}
This works, but is inefficient, as CarType is looked up from DB every time a Car is loaded. I would like to cache car types in memory, as they change rarely. Persistence frameworks like GORM and Hibernate would allow that out of the box, but I'm not sure how to do it under Morphia (there is a feature request raised for that).
I'd like to keep the reference to CarType, as just storing a String carTypeId would complicate the views and everything else too much.
So I thought I could do something like this:
#Entity
class Car {
#Id private String id; // VIN
private Date purchaseDate;
private Color color;
private String typeId;
#Transient private CarType type;
#Transient private CarService service = new CarServiceImpl();
public void setTypeId() {
this.typeId = typeId;
updateTypeReference();
}
#PostLoad void postLoad() {
updateTypeReference();
}
private void updateTypeReference() {
type = service.findTypeById(typeId);
}
// ..
}
class CarServiceImpl implements CarService {
#CacheResult CarType findCarTypeId(String typeId) {
datastore.get(CarType.class, typeId);
}
// ..
}
Which works and does what I want, but:
Does seem like a hack
I'd to inject the service instead using Guice, but cannot figure out how, although I have overall dependency injection working in Stripes ActionBeans.
So I'd like to either:
Learn how to inject (preferably, Guice) services into Morphia entities
or
Learn how to otherwise properly do caching for referenced entities in Morphia
or
If all else fails, switch to some other MongoDB POJO mapping approach which supports caching. But I really like Morphia so I'd rather not.
Another common approach would be to embed the CarType in each Car. That way you would only have to fetch a single entity.
Trade-offs:
You'll need an update logic for all duplicated CarTypes. Since you said that they hardly change, this should be fine performance-wise.
Duplicated data requires additional disk-space and the working set in RAM gets bigger as well.
You'll need to evaluate how this works out for your data, but data duplication to make reads faster is quite a common approach...
Since I didn't think of a better solution I am doing a #PostLoad event handler which gets the datastore class from a static variable, and can then look up the Referenced entity.
That seems like a hack and requires the datastore service to be thread-safe, but it works for me.
Related
I am trying to create a subtype query along the following lines, but tyre is coming back as null even if I set #QueryInit("tyre") on the wheel property of car.
QWheel wheel = QCar.car.wheel;
QTyre tyre = wheel.as(QRoadWheel.class).tyre;
BooleanExpression tyreFittedOverYearAgo
= tyre.fitted.lt(today.minusYears(1));
Iterable<Car> carsWithOldTyres = repo.findAll(tyreFittedOverYearAgo);
How do I get QueryDSL to initialise tyre when it is accessed using as()?
By default Querydsl initializes only direct reference properties. In cases where longer initialization paths are required, these have to be annotated in the domain types via com.mysema.query.annotations.QueryInit usage. QueryInit is used on properties where deep initializations are needed.
#Entity
class Event {
#QueryInit("customer")
Account account;
}
#Entity
class Account{
Customer customer;
}
#Entity
class Customer{
String name;
String address;
}
This will intialize customer.name ,customer.address
I've not been able to establish why, but I've now got things working but by using:
#QueryInit("*")
Tyre tyre;
Say I have a #MappedSuperClass like this:
#MappedSuperclass
public abstract class Rating
{
#Id
private Long id;
#Column(name="USER_ID")
private Long userId;
private int rating;
...
With a concrete child entity like this
#Entity
#Table(name="ACTIVITY_RATING")
public class ActivityRating extends Rating
{
private Long activitySpecificData;
...
Then there is a Spring Data JPA repository like this:
#NoRepositoryBean
public interface RatingRepository<R extends Rating> extends JpaRepository<R, ID>
{
public List<R> findByUserId(Long userId);
...
and this:
public interface ActivityRatingRepository extends RatingRepository<ActivityRating>
{
}
This all works great and I can call findByUserId() on any of specific rating repositories that extend RatingRepository. I am now wanting to write some JPQL in the RatingRepository that all the child interfaces can inherit. I just don't know what (or if it's even possible) to put after the FROM in the query. For example:
#Query("SELECT NEW com.foo.RatingCountVo(e.rating, COUNT(e.rating)) FROM ??????? e GROUP BY e.rating")
public List<RatingCountVo> getRatingCounts();
I can add this method to each of the individual repositories that extend RatingRepository but everything would be exactly the same except for the specific entity name. If I want to change the query, I'd then have to go to all the child repositories and update them individually. I really want the query to live in the parent class and not be duplicated. Is there any way to accomplish this?
I'm currently using spring-data-jpa 1.7.2 and eclipselink 2.5.2. I'm not necessarily opposed to switching to newer versions if necessary.
Will it work if you will split query into 3 parts: start, entity and end of query? Than, if it'll work, in each interface you define constant like
String ENTITY = "ActivityRating";
And then you can use it like
#Query(RatingRepository.QUERY_START + ENTITY + RatingRepository.QUERY_END)
List<RatingCountVo> getRatingCounts();
BTW, there is no need to define public modifier in interface.
UPDATE: here is described another way:
#Query("SELECT NEW com.foo.RatingCountVo(e.rating, COUNT(e.rating)) FROM #{#entityName} e GROUP BY e.rating
I have this #ElementCollection mapping so i could bring a legacy table with no unique id to work:
#Entity #Table(...)
#Inheritance(...) #DiscriminatorColumn(...)
class Notification {
#Id
#Column(name="NOTIFICATION_ID")
private BigInteger id;
}
#Entity
#DiscriminatorValue(...)
class SomeNotification extends Notification {
#ElementCollection
#CollectionTable(name="LEGACY_TABLE", joinColumns=#JoinColumn(name="NOTIFICATION_ID"))
private Set<NotificationInfo> someInformations;
}
#Embeddable
class NotificationInfo { // few columns }
I really can't touch the structure of LEGACY_TABLE, and now i am facing this:
#Entity
#DiscriminatorValue(...)
class SpecialNotification extends Notification {
// ? This is not a Collection, and it can't be a ManyToOne or OneToOne
// since there is no ID declared on NotificationInfo.
private NotificationInfo verySpecialInformation;
}
I know this is not supported by default, but i am fine to implement a Customizer to make it work with EclipseLink. The point is that for SpecialNotification instances, there will be only up to one NotificationInfo associated, instead of many, that is the case of SomeNotification.
Any thoughts about where i could start in the Customizer?
Thank you!
I'm not sure this will work, but it's worth a shot. Try a combination of #SecondaryTable and #AttributeOverride
#Entity
#SecondaryTable(name="LEGACY_TABLE",
pkJoinColumns=#PrimaryKeyJoinColumn(name="NOTIFICATION_ID"))
#DiscriminatorValue(...)
class SpecialNotification extends Notification {
...
#Embedded
#AttributeOverrides({
#AttributeOverride(name="someField", column=#Column(table = "LEGACY_TABLE", name="SOME_FIELD")),
#AttributeOverride(name="someOtherField", column=#Column(table = "LEGACY_TABLE", name="SOME_OTHER_FIELD"))
})
private NotificationInfo verySpecialInformation;
...
}
UPDATE
Since #SecondaryTable by default makes an inner join, which may not be desired, it can be worked around with vendor specific APIs.
If you use Hibernate (which you don't, judging by the question tags, but nevertheless), it can be done with #org.hibernate.annotations.Table, by setting optional = true.
With EclipseLink, you should make use of #DescriptorCustomizer and DescriptorQueryManager#setMultipleTableJoinExpression, you can find a (not spot-on, but close enough) code example here.
I am designing a system that has a simple Entity Framework backed domain object that has fields I need to update based on a series of rules - I want to implement these rules progressively (in an agile style) and as I am using EF I am sceptical about putting each rule into the domain object. However, I want to avoid writing "procedural code" and using anemic domain models. This all needs to be testable as well.
As an example, the object is:
class Employee {
private string Name;
private float Salary;
private float PensionPot;
private bool _pension;
private bool _eligibleForPension;
}
I need to build rules such as "if Salary is higher than 100,000 and _eligibleForPension is false then set _eligibleForPension as true" and "if _pension is true then set _eligibleForPension as true".
There are approximately 20 such rules and I am looking for advice whether they should be implemented in the Employee class or in something like an EmployeeRules class? My first thought was to create a separate class for each rule inheriting from "Rule" and then apply each rule to the Employee class, maybe using the Visitor pattern but I'd have to expose all the fields to the rules to do this so it feels wrong. Having each rule on the Employee class though doesn't feel quite right either. How would this be implemented?
The second concern is that the actual Employees are Entity Framework entities backed to the DB so I don't feel happy adding logic to these "Entities" - especially when I need to mock the objects for unit testing each rule. How could I mock them if they have the rules I'm testing on the same object?
I have been thinking of using AutoMapper to convert to a simpler domain object before applying rules but then need to manage the updates to the fields myself. Any advice on this too?
One approach is to make the rules inner classes of Employee. The benefit of this approach is that the fields can remain private. Also, the invocation of the rules can be enforced by the Employee class itself, ensuring that they are always invoked when needed:
class Employee
{
string id;
string name;
float salary;
float pensionPot;
bool pension;
bool eligibleForPension;
public void ChangeSalary(float salary)
{
this.salary = salary;
ApplyRules();
}
public void MakeEligibleForPension()
{
this.eligibleForPension = true;
ApplyRules(); // may or may not be needed
}
void ApplyRules()
{
rules.ForEach(rule => rule.Apply(this));
}
readonly static List<IEmployeeRule> rules;
static Employee()
{
rules = new List<IEmployeeRule>
{
new SalaryBasedPensionEligibilityRule()
};
}
interface IEmployeeRule
{
void Apply(Employee employee);
}
class SalaryBasedPensionEligibilityRule : IEmployeeRule
{
public void Apply(Employee employee)
{
if (employee.salary > 100000 && !employee.eligibleForPension)
{
employee.MakeEligibleForPension();
}
}
}
}
One problem here is that the Employee class has to contain all rule implementations. This isn't a major problem since the rules embody business logic associated with employee pensions and so they do belong together.
Business rules are usually an interesting topic. There may certainly be a difference between an aggregate / entity invariant and a business rule. Business rules may need external data and I wouldn't agree with a rule changing an aggregate / entity.
You should think specification pattern for rules. The rule should basically just return whether it was broken or not with possibly a description of sorts.
In your example SalaryBasedPensionEligibilityRule, as used by eulerfx, may need some PensionThreshold. This rule really does look more like a task since the rule really isn't checking any validity of the entity.
So I would suggest that rules are a decision mechanism and tasks are for changing the state.
That being said you probably want to ask the entity for advice here since you may not want to expose the state:
public class Employee
{
float salary;
bool eligibleForPension;
public bool QualifiesForPension(float pensionThreshold)
{
return salary > pensionThreshold && !eligibleForPension;
}
public void MakeEligibleForPension()
{
eligibleForPension = true;
}
}
This sticks with the command/query separation idea.
If you are building directly from your ORM objects and do not want to, or cannot, include all the behaviour then that is OK --- but it certainly would help :)
I have two entities. "Price" class has "CalculableValue" stored as SortedMap field.
In order to support sorted map I wrote customizer. After that, it seems #CascadeOnDelete is not working. If I remove CalculableValue instance from map and then save "Price" EclipseLink only updates priceId column to NULL in calculableValues table...
I really want to keep the SortedMap. It helps to avoid lots of routine work for values access on Java level.
Also, there is no back-reference (ManyToOne) defined in the CalculableValue class, it will never be required for application logic, so, wanted to keep it just one way.
Any ideas what is the best way to resolve this issue? I actually have lots of other dependencies like this and pretty much everything is OneToMany relation with values stored in sorted map.
Price.java:
#Entity
#Table(uniqueConstraints={
#UniqueConstraint(columnNames={"symbol", "datestring", "timestring"})
})
#Customizer(CustomDescriptorCustomizer.class)
public class Price extends CommonWithDate
{
...
#CascadeOnDelete
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#MapKeyColumn(name="key")
#JoinColumn(name = "priceId")
private Map<String, CalculatedValue> calculatedValues =
new TreeMap<String, CalculatedValue>();
...
}
public class CustomDescriptorCustomizer implements DescriptorCustomizer
{
#Override
public void customize(ClassDescriptor descriptor) throws Exception
{
DatabaseMapping jpaMapping = descriptor.getMappingByAttribute("calculatedValues");
((ContainerMapping) mapping).useMapClass(TreeMap.class, methodName);
}
}
Your customizer should have no affect on this. It could be because you are using a #JoinColumn instead of using a mappedBy which should normally be used in a #OneToMany.
You can check the mapping in your customizer using, isCascadeOnDeleteSetOnDatabase()
or set it using
mapping.setIsCascadeOnDeleteSetOnDatabase(true)