Persistent storage
1. Database: JPA and Hibernate
Enrich domain POJOs (solution, entities and problem facts) with JPA annotations
to store them in a database by calling EntityManager.persist().
|
Do not confuse JPA’s
|
1.1. JPA and Hibernate: persisting a Score
The timefold-solver-jpa jar provides a JPA score converter for every built-in score type.
@PlanningSolution
@Entity
public class VehicleRoutePlan {
@PlanningScore
@Convert(converter = HardSoftScoreConverter.class)
protected HardSoftScore score;
...
}
Please note that the converters make JPA and Hibernate serialize the score in a single VARCHAR column.
This has the disadvantage that the score cannot be used in a SQL or JPA-QL query to efficiently filter the results, for example to query all infeasible schedules.
To avoid this limitation, implement the CompositeUserType to persist each score level into a separate database table column.
1.2. JPA and Hibernate: planning cloning
In JPA and Hibernate, there is usually a @ManyToOne relationship from most problem fact classes to the planning solution class.
Therefore, the problem fact classes reference the planning solution class,
which implies that when the solution is planning cloned, they need to be cloned too.
Use an @DeepPlanningClone on each such problem fact class to enforce that:
@PlanningSolution // Timefold Solver annotation
@Entity // JPA annotation
public class Conference {
@OneToMany(mappedBy="conference")
private List<Room> roomList;
...
}
@DeepPlanningClone // Timefold Solver annotation: Force the default planning cloner to planning clone this class too
@Entity // JPA annotation
public class Room {
@ManyToOne
private Conference conference; // Because of this reference, this problem fact needs to be planning cloned too
}
Neglecting to do this can lead to persisting duplicate solutions, JPA exceptions or other side effects.
2. XML or JSON: JAXB
Enrich domain POJOs (solution, entities and problem facts) with JAXB annotations to serialize them to/from XML or JSON.
Add a dependency to the timefold-solver-jaxb jar to take advantage of these extra integration features:
2.1. JAXB: marshalling a Score
When a Score is marshalled to XML or JSON by the default JAXB configuration, it’s corrupted.
To fix that, configure the appropriate ScoreJaxbAdapter:
@PlanningSolution
@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD)
public class VehicleRoutePlan {
@PlanningScore
@XmlJavaTypeAdapter(HardSoftScoreJaxbAdapter.class)
private HardSoftScore score;
...
}
For example, this generates pretty XML:
<vehicleRoutePlan>
...
<score>0hard/-200soft</score>
</vehicleRoutePlan>
The same applies for a bendable score:
@PlanningSolution
@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD)
public class Schedule {
@PlanningScore
@XmlJavaTypeAdapter(BendableScoreJaxbAdapter.class)
private BendableScore score;
...
}
For example, with a hardLevelsSize of 2 and a softLevelsSize of 3, that will generate:
<schedule>
...
<score>[0/0]hard/[-100/-20/-3]soft</score>
</schedule>
The hardLevelsSize and softLevelsSize implied, when reading a bendable score from an XML element, must always be in sync with those in the solver.
3. JSON: Jackson
Enrich domain POJOs (solution, entities and problem facts) with Jackson annotations to serialize them to/from JSON.
Add a dependency to the timefold-solver-jackson jar and register TimefoldJacksonModule:
var objectMapper = JsonMapper.builder()
.addModule(TimefoldJacksonModule.createModule())
.build();
3.1. Jackson: marshalling a Score
When a Score is marshalled to/from JSON by the default Jackson configuration, it fails.
The TimefoldJacksonModule fixes that, by using HardSoftScoreJacksonSerializer,
HardSoftScoreJacksonDeserializer, etc.
@PlanningSolution
public class VehicleRoutePlan {
@PlanningScore
private HardSoftScore score;
...
}
For example, this generates:
{
"score":"0hard/-200soft"
...
}
|
When reading a
This JSON implies the
|
When a field is the Score supertype (instead of a specific type such as HardSoftScore),
it uses PolymorphicScoreJacksonSerializer and PolymorphicScoreJacksonDeserializer to record the score type in JSON too,
otherwise it would be impossible to deserialize it:
@PlanningSolution
public class VehicleRoutePlan {
@PlanningScore
private Score score;
...
}
For example, this generates:
{
"score":{"HardSoftScore":"0hard/-200soft"}
...
}