I am trying to integrate OptaPlanner in my project. I am working with Spring jpa, maven and mysql database.
I have implemented the dependencies on my maven file, so I can use the annotations of OptaPlanner, but I don't know how to use it. I have been reading the documentation and examples but i still don't know how to use it.
I have to assign recipes and an user to a class called FoodList. Each object of FoodList has id, 2 enums, the recipe, the user and a Date, i show:
FoodList class:
#PlanningEntity()
#Entity
public class ListaComida {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Enumerated(EnumType.STRING)
private Comida comida;
#Enumerated(EnumType.STRING)
private Plato plato;
#PlanningVariable()
#ManyToOne
private Receta receta;
#PlanningVariable()
#ManyToOne
private Usuario usuario;
#Column(nullable = false)
private LocalDate fecha;
...
}
#PlanningSolution // OptaPlanner annotation
#TypeDef(defaultForType = HardSoftScore.class, typeClass = HardSoftScoreHibernateType.class) // Hibernate annotation
public class ListaComidaSolution {
#Columns(columns = {#Column(name = "hardScore"), #Column(name = "softScore")})
private HardSoftScore score;
#PlanningScore
public HardSoftScore getScore() {
return score;
}
public void setScore(HardSoftScore score) {
this.score = score;
}
}
<!-- Score configuration -->
<scoreDirectorFactory>
<easyScoreCalculatorClass>src/main/java/es.uca.AutomaticFoodList/GenerarComidaEasyScoreCalculator</easyScoreCalculatorClass>
<!--<scoreDrl>org/optaplanner/examples/cloudbalancing/solver/cloudBalancingScoreRules.drl</scoreDrl>-->
</scoreDirectorFactory>
<!-- Optimization algorithms configuration -->
<termination>
<secondsSpentLimit>30</secondsSpentLimit>
</termination>
public class GenerarComidaEasyScoreCalculator implements EasyScoreCalculator<ListaComidaSolution> {
public HardSoftScore calculateScore(ListaComidaSolution listaComidaSolution){
int hardScore = 0, softScore = 0;
return HardSoftScore.of(hardScore, softScore);
}
}
This class is not implemented, but I think I have to do it.
public static void generarListaComida(){
//SolverFactory<CloudBalance> solverFactory = SolverFactory.createFromXmlResource(
// "org/optaplanner/examples/cloudbalancing/solver/cloudBalancingSolverConfig.xml");
//Solver<CloudBalance> solver = solverFactory.buildSolver();
// Load a problem with 400 computers and 1200 processes
//CloudBalance unsolvedCloudBalance = new CloudBalancingGenerator().createCloudBalance(400, 1200);
// Solve the problem
//CloudBalance solvedCloudBalance = solver.solve(unsolvedCloudBalance);
// Display the result
//System.out.println("\nSolved cloudBalance with 400 computers and 1200 processes:\n"
// + toDisplayString(solvedCloudBalance));
}
Is this all classes and files I need to implement this in my project?, or I have to implement more classes?
On https://www.optaplanner.org/ you can download an executable demo. However it is not just an executable demo but also contains the source code of the examples ( in examples/source folder). There you can see how optaplanner is used in the example applications, and you can do the same in your application.
A good starting point is also https://docs.optaplanner.org/7.36.0.Final/optaplanner-docs/html_single/index.html#plannerConfiguration chapter 4 ff.
Related
I seem to be unable to add QueryByExample probes that match related entities.
#Entity
#Data
public class ArtistEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
#ManyToMany(fetch = FetchType.EAGER)
private Set<GenreEntity> genreList = new HashSet<>();
#Version
private Long version;
}
#Entity
#Data
public class GenreEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#Version
private Long version;
}
#Repository
public interface ArtistRepository extends JpaRepository<ArtistEntity, Long> {
}
When I try the following query, according to Hibernate's logs, my probe isn't running any conditions against the Genre
GenreEntity genreEntity = new GenreEntity();
genreEntity.setName("Heavy Metal");
ArtistEntity artistEntity = new ArtistEntity();
Set<GenreEntity> genreEntitySet = new HashSet<>();
genreEntitySet.add(genreEntity);
artistEntity.setGenreList(genreEntitySet);
Example<ArtistEntity> example = Example.of(artistEntity);
Pageable pagination = PageRequest.of(0, 10);
artistRepository.findAll(example, pagination);
I also tried looking on the Spring Data JPA documentation regarding QBE, but I didn't find anything specifically mentioning this limitation, which brought me to assume it's an unexpected behaviour.
Currently, you cannot do this with Query By Example.
The spring document states that this only works with SingularAttribute.
Currently, only SingularAttribute properties can be used for property
matching. https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#query-by-example.running
You want to search by a property that is a Set<GenreEntity> (genreList), which is a PluralAttribute. It is not possible to search by this field.
It will be ignored when building a query, as can be seen here: https://github.com/spring-projects/spring-data-jpa/blob/master/src/main/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilder.java#L127
You can use Specification.
Advanced Spring Data JPA - Specifications and Querydsl. https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
For this you need to extend from interface JpaSpecificationExecutor:
public interface ArtistRepository extends JpaRepository<ArtistEntity>, JpaSpecificationExecutor<ArtistEntity> {
}
And you also need to implement your custom Specification<T>.
And then you can use findAll(Specification<T> spec, Pageable pageable).
You may just use this library which supports nested fields and much more: https://github.com/turkraft/spring-filter
It will let you run search queries such as:
/search?filter= average(ratings) > 4.5 and brand.name in ('audi', 'land rover') and (year > 2018 or km < 50000) and color : 'white' and accidents is empty
I am making a Spring Boot backend, and I have the following problem. When I get a Software from VersionableFileRepository and call the getSystem function on that I get the actual System within the relationship. But when I get a Documentation from VersionableFileRepository its getSystem function returns null. I handle the Software and Documentation in the same way, and all instance of these have a System.
Illustrated with code:
versionableFileRepository.findById(fileId).get().getSystem() returns a valid System when fileId identify a Software and returns null when a Documentation
What's wrong? Did I mess something up in the implementation?
I have the following classes:
#Entity
public class System {
#Id
#GeneratedValue
private long id;
private String name;
#OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
#JoinColumn(name = "software_id", referencedColumnName = "id")
private Software software;
#OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
#JoinColumn(name = "documentation_id", referencedColumnName = "id")
private Documentation documentation;
//other fields, getters and setters...
}
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class VersionableFile {
#Id
#GeneratedValue
private long id;
#OneToMany(mappedBy = "file", cascade = CascadeType.REMOVE, orphanRemoval = true)
private List<FileVersion> versions = new ArrayList<>();
public abstract System getSystem();
public abstract void setSystem(System system);
//getters and setters...
}
#Entity
public class Software extends VersionableFile {
#OneToOne(mappedBy = "software")
#JsonIgnore
private System system;
#Override
public System getSystem() {
return system;
}
#Override
public void setSystem(System system) {
this.system = system;
}
}
#Entity
public class Documentation extends VersionableFile {
#OneToOne(mappedBy = "documentation")
#JsonIgnore
private System system;
#Override
public System getSystem() {
return system;
}
#Override
public void setSystem(System system) {
this.system = system;
}
}
#Repository
public interface VersionableFileRepository extends CrudRepository<VersionableFile, Long> {
}
Database:
Everything looks good in the database, this is the system table:
And the corresponding objects can be found in the other two tables (software and documentation). Furthermore the appropriate constraints are also defined.
I think this is a JPA issue, because when I get a System object from SystemRepository (not mentioned here) it has the right software and documentation fields.
Thank you in advance for your help!
Have already commented but looking better I think I found something major here.
Proposal 1
Your Entities structure seems good to me. However you have a major Issue with your java code to retrieve those entities back from database.
versionableFileRepository.findById(fileId).get().getSystem()
fileId as well as documentId are plain Long numbers. How would JPA know if you want to retrieve a Software or a Documentation? This will not work. As you have constructed it, it will have separate tables Documentation and Software and each one of those will have a column Id as primary key.
Make it easier for JPA by using specific repositories
#Repository
public interface SoftwareRepository extends CrudRepository<Software, Long> {
}
Then to retrieve software just use softwareRepository.findById(id).get().getSystem()
And
#Repository
public interface DocumentationRepository extends CrudRepository<Documentation, Long> {
}
Then to retrieve documentation just use documentationRepository.findById(id).get().getSystem()
Proposal 2
If you wish to go along the way you are going then I would consider that the error is specifically on your ids that are generated. You want different tables in your case Documentation and Software to have distinct Ids. Then JPA could distinct from the Id what entity you have.
To achieve that you have to change the strategy of generating Ids
public abstract class VersionableFile {
#Id
#GeneratedValue( strategy = GenerationType.TABLE)
private long id;
....
My problem is that even if I take ping etc. into account, the following API request takes 10 times longer in my production environment(remote database(AWS), docker, etc.) with the same amount of data. It takes around 150ms when I run it on localhost and 1.5-2s when I run it on production. What might cause that huge difference? Like I said its the same amount of data, the ping, and the traffic transmitted(only 5kb) shouldn't cause that. I also only have two SQL queries I manually call. A lot of more SQL queries are automatically called because of the OneToMany relation you can see below. But I can't think of why that would cause any difference in performance between localhost and production.
Thats the API method i call:
#GetMapping
public ResponseEntity<List<Project>> getProjects(#RequestParam(required = false) boolean additionalInfo) {
return ResponseEntity.ok(projectService.getProjects(additionalInfo));
}
Inside the service:
private List<Project> getProjects(boolean additionalInfo) {
List<Project> projects = (List<Project>) projectRepository.findAll();
if (additionalInfo) {
for (Project project : projects) {
setAdditionalInfo(project);
}
}
return projects;
}
private void setAdditionalInfo(Project project) {
Integer notificationCount = 0;
Duration duration = Duration.ZERO;
LocalDate lastDailyEntryDate = LocalDate.MIN;
for (DailyEntry dailyEntry : dailyEntryRepository.findByProjectId(project.getId())) {
if (dailyEntry.getStatus().equals(EntryStatus.OPEN)) {
notificationCount++;
}
duration = duration.plus(Duration.between(dailyEntry.getStartTime(), dailyEntry.getEndTime()));
if (lastDailyEntryDate.isBefore(dailyEntry.getDate())) {
lastDailyEntryDate = dailyEntry.getDate();
}
}
int inactiveSince = calculateInactiveSince(lastDailyEntryDate, project);
if (inactiveSince >= 3 && !project.getIsArchived()) {
project.setInactiveSince(inactiveSince);
}
project.setNotificationCount(notificationCount);
project.setUsedBudget(duration.toHours() * 100);
}
private Integer calculateInactiveSince(LocalDate lastDailyEntryDate, Project project) {
int inactiveSince;
if (lastDailyEntryDate != LocalDate.MIN) {
Period period = Period.between(lastDailyEntryDate, LocalDate.now());
inactiveSince = period.getYears() * 12 + period.getMonths();
} else {
Period period = Period.between(project.getCreationDate(), LocalDate.now());
inactiveSince = period.getYears() * 12 + period.getMonths();
}
return inactiveSince;
}
Thats how the entites look like:
Project:
#Data
#Entity
#ToString(exclude = {"employees"})
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(unique = true)
private String name;
private Integer budget;
private String description;
private Boolean isArchived;
private LocalDate archivedDate;
private LocalDate creationDate;
#NotNull
#ManyToOne
private Customer customer;
#ManyToMany
#JoinTable(
name = "employee_projects",
joinColumns = #JoinColumn(name = "project_id"),
inverseJoinColumns = #JoinColumn(name = "employee_id")
)
private List<Employee> employees;
#Transient
private Long usedBudget;
#Transient
private Integer notificationCount;
#Transient
private Boolean isInactive;
#Transient
private Integer inactiveSince;
}
DailyEntry:
#Data
#Entity
#JsonInclude(JsonInclude.Include.NON_NULL)
public class DailyEntry {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private LocalDate date;
private LocalTime startTime;
private LocalTime endTime;
private Duration breaks;
private String performanceRecord;
private EntryStatus status;
#ManyToOne
private Project project;
#ManyToOne
private Employee employee;
}
By default Hibernate FetchType is EAGER (it fetched the child entities along with a parent).
If you really need child data too you can either use: Cache or Pagination or use EntityGraph
Or you can just Set FetchType.Eager.
for example:
#NotNull
#ManyToOne(fetch = FetchType.Lazy)
private Customer customer;
This kind of issues can be solved by elimination of the parts that contribute to the slowness:
Network
Self Service processing time (your server code)
Database calls
First of all lets eliminate the network (maybe you're trying to reach the server that is in another end of the World)
So you can use spring boot actuator that has an endpoint that shows the information about last 50 requests, including the execution time. This time is measured from the point the request reaches the server till the time that response is sent.
Now, if you see "1.5-2 seconds" there, its on server, otherwise check the network.
The self-processing should be the same unless you're processing a different amount of data which is not true as you state, so I move on.
Regarding the Database calls. This is a very broad topic, in general there can be many reasons:
The DB server works much busier in production, because it has to serve much more requests (coming from your flow or other flows, or maybe other applications configured to work with this DB).
The hardware that runs the DB is slow/weak
The tables lack some index and the requests are slow.
One way or another this has to be thoroughly checked.
I believe the best way is to measure the query execution time and place this data to some log.
There are many ways to implement such a log, starting with "manually" measuring the time, and ending with wrapping the Repository with a logging proxy like stated in this SO thread
projectRepository.findAll() is the root cause.
You should never do this, rather invoke through pagination:
projectRepository.findAll(Pageable p);
I want to compare two attributes using JPA method convention.
This is my class
#Entity
#Table(name = "aircrafts")
public class Aircrafts {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Size(max = 45)
#Column(name = "number", length = 45)
private String number;
#Column(name = "capacity")
private int capacity;
#Column(name = "seats_taken")
private int seatsTaken;
}
And this the method I want to implement :
public interface AircraftsRepository extends JpaRepository<Aircrafts, Long> {
public List<Aircrafts> findBySeatsTakenLessThanCapacity();
}
However I got this exception:
PropertyReferenceException: No property lessThanCapacity found for type int! Traversed path: Aircrafts.seatsTaken.
I've tried using int and Integer but I got the same exception. Which is the correct method name?
I think #benji2505, correctly mentioned that the data model mixes things that "should" be in different tables. Normally one would expect two tables: Aircrafts, Flights.
Then you could easily use:
List<Flight> flightsWithFreeSeats = aircraftRepository
.findAll()
.stream()
.map(aircraft ->
flightRepository.findByAircraftAndSeatsTakenLessThen(aircraft, aircraft.getCapacity)
)
.collect(Collectors.toList)
With current model probably #JZ Nizet already posted the best answer in his comment.
Hi I could need a little help understanding how I should approach following:
Just for information I'm working with play framework 2.0 and Ebeans version ~2.7
I have a model projects and a model reports. A project can have many reports but a report can only belong to one project.
This is my Projects Model:
#Entity
#Table(name = "Projects")
public class Projects extends Model {
private static final long serialVersionUID = -3343133304437498550L;
#Id
public long id;
#NotNull
#Size(min = 1, max = 255)
public String name;
#NotNull
#Size(min = 1, max = 255)
public String description;
#Formats.DateTime(pattern = "dd/MM/yyyy")
public Date created_at = new Date();
public static Finder<Long, Projects> find = new Finder<Long, Projects>(Long.class, Projects.class);
}
And this is my Reports Model:
#Entity
#Table(name = "Reports")
public class Reports extends Model {
private static final long serialVersionUID = -6617128169471038678L;
#Id
public long id;
#Constraints.Required
public String description;
#Formats.DateTime(pattern = "dd/MM/yyyy")
public Date created_at = new Date();
#ManyToOne
#NotNull
public Projects project;
public static Finder<Long, Reports> find = new Finder<Long, Reports>(Long.class, Reports.class);
}
The saving works without a problem I can create a Project and I can create multiple reports that point to the right projects. My question know is how would I go about querying this.
When I have a project how would I get all related reports to it? I think I could figure it out as a plain SQL-Query but I'm pretty sure it should be possible with ebean.
Best regards and thank you for your time!
UPDATE:
This is how I would do it with a sql query
SELECT * FROM `Projects` LEFT JOIN `Reports` ON Projects.`id` = Reports.`id`;
I can't try it right now, but I would do something like this:
public class Reports extends Model {
...
public static List<Reports> findByProject(Projects project) {
return find.fetch("project").where().eq("project.id", project.id).findList();
}
}