I have a problem with planning employees shifts where employees are distributed uniformly (randomly) across the shifts.
In my minimal example I use Spring boot, Lombock and Optaplanner spring boot starter (8.15.0.Final) package.
My minimal example in one file:
package com.example.planner;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.optaplanner.core.api.domain.entity.PlanningEntity;
import org.optaplanner.core.api.domain.lookup.PlanningId;
import org.optaplanner.core.api.domain.solution.PlanningEntityCollectionProperty;
import org.optaplanner.core.api.domain.solution.PlanningScore;
import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.api.domain.solution.ProblemFactCollectionProperty;
import org.optaplanner.core.api.domain.valuerange.ValueRangeProvider;
import org.optaplanner.core.api.domain.variable.PlanningVariable;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
import org.optaplanner.core.api.score.stream.Constraint;
import org.optaplanner.core.api.score.stream.ConstraintFactory;
import org.optaplanner.core.api.score.stream.ConstraintProvider;
import org.optaplanner.core.api.solver.SolverManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.util.ArrayList;
import java.util.List;
#SpringBootApplication
public class PlannerApplication implements CommandLineRunner {
#Autowired
private SolverManager<Problem, Long> solverManager;
public static void main(String[] args) {
SpringApplication.run(PlannerApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
final var problem = new Problem(
List.of(new Employee(1L), new Employee(2L), new Employee(3L)),
List.of(new Shift(1L), new Shift(2L), new Shift(3L), new Shift(4L), new Shift(5L), new Shift(6L))
);
final var job = solverManager.solveAndListen(1L, id -> problem, bestSolution -> {
for (final var shift : bestSolution.shifts) {
System.err.println("Shift " + shift.id + ": Employee " + shift.employee.id);
}
});
}
#NoArgsConstructor
public static class PlannerConstraintProvider implements ConstraintProvider {
#Override
public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
return new Constraint[]{};
}
}
#PlanningSolution
#Data #NoArgsConstructor #AllArgsConstructor
public static class Problem {
#ValueRangeProvider(id = "employeeRange")
#ProblemFactCollectionProperty
private List<Employee> employees;
#PlanningEntityCollectionProperty
private List<Shift> shifts = new ArrayList<>(0);
#PlanningScore
private HardSoftScore score;
public Problem(List<Employee> employees, List<Shift> shifts) {
this.employees = employees;
this.shifts = shifts;
}
}
#Data #NoArgsConstructor #AllArgsConstructor
public static class Employee {
#PlanningId
private Long id;
}
#PlanningEntity
#Data #NoArgsConstructor #AllArgsConstructor
public static class Shift {
#PlanningId
private Long id;
#PlanningVariable(valueRangeProviderRefs = "employeeRange")
private Employee employee;
public Shift(Long id) {
this.id = id;
}
}
}
Output of this example is:
Shift 1: Employee 1
Shift 2: Employee 1
Shift 3: Employee 1
Shift 4: Employee 1
Shift 5: Employee 1
Shift 6: Employee 1
Desired output is:
Shift 1: Employee 1
Shift 2: Employee 2
Shift 3: Employee 3
Shift 4: Employee 1
Shift 5: Employee 2
Shift 6: Employee 3
(or another uniform combinations)
You haven't defined any constraints, therefore OptaPlaner has no reason to come up with a better solution. You are not telling it what is better.
OptaPlanner "thinks" this solution is the best possible because (I guess) it has a score of 0hard/0soft (you can check it in the console), which is an ideal score.
To achieve the desired output you should define a fair workload distribution constraint that will penalize each employee with a square of its workload. Probably something like this should work in your case:
#Override
public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
return new Constraint[] {
fairWorkloadDistribution(constraintFactory)
};
}
...
Constraint fairWorkloadDistribution(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(Shift.class)
.groupBy(Shift::getEmployee, ConstraintCollectors.count())
.penalize(
"Employee workload squared",
HardSoftScore.ONE_SOFT,
(employee, shifts) -> shifts * shifts);
}
You have no constraints. You have not told OptaPlanner what to optimize for, and therefore all solutions are equally favorable to OptaPlanner.
(In fact, I am quite surprised that this code does not fail. A situation with no constraints should have thrown an exception.)
Related
When I try to execute a query on a GSI on DynamoDB, the following error is reported:
java.lang.IllegalArgumentException: A query conditional requires a sort key to be present on the table or index being queried, yet none have been defined in the model
What I should do in order to define this required sort key?
Here are some pieces of my code:
package com.test;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.*;
import java.time.Instant;
#Data
#Builder
#DynamoDbBean
#NoArgsConstructor
#AllArgsConstructor
#ToString
public class Accumulator {
private Instant createdAt;
private String userId;
private String transactionIds;
private String paymentAt;
private String type;
private Instant startAt;
private Instant endAt;
private Integer ordersTotal;
private Integer retries;
private String sortKey;
#DynamoDbPartitionKey
public String getUserId() {
return userId;
}
#DynamoDbSecondaryPartitionKey(indexNames= {"idx_by_payment_at"})
#DynamoDbSortKey
public String getPaymentAt() {
return paymentAt.toString();
}
}
Creating my table at DynamoDBConfig:
public DynamoDbAsyncTable<Accumulator> tableLocal(final DynamoDbEnhancedAsyncClient dynamoDbEnhancedAsyncClient) {
var tableAccumulator = dynamoDbEnhancedAsyncClient.table(tablename, BeanTableSchema.create(Accumulator.class));
try {
tableAccumulator.createTable(CreateTableEnhancedRequest.builder()
.provisionedThroughput(
ProvisionedThroughput.builder()
.writeCapacityUnits(5L).readCapacityUnits(5L)
.build())
.globalSecondaryIndices(
EnhancedGlobalSecondaryIndex.builder()
.indexName("idx_by_payment_at")
.projection(p -> p.projectionType(ProjectionType.KEYS_ONLY))
.provisionedThroughput(ProvisionedThroughput.builder()
.writeCapacityUnits(5L).readCapacityUnits(5L)
.build())
.build()
)
.build()).get();
Thread.sleep(3000);
} catch (InterruptedException | ExecutionException e) {
log.info("Skipping");
}
return tableAccumulator;
}
And my query:
DynamoDbTable<Accumulator> table = dynamoDbEnhancedClient.table(tableName, TableSchema.fromBean(Accumulator.class));
DynamoDbIndex<Accumulator> index = table.index("idx_by_payment_at");
QueryConditional queryConditional = QueryConditional.sortBetween(
Key.builder()
.partitionValue("2022-01-23T06:10:12.948334Z")
.build(),
Key.builder()
.partitionValue("2022-01-23T06:10:22.515769Z")
.build());
SdkIterable<Page<Accumulator>> query = index.query(queryConditional);
List<Page<Accumulator>> pages = query.stream().toList();
pages.forEach(page -> {
List<Accumulator> accumulators = page.items();
accumulators.stream().forEach(accumulator -> {
System.out.println(accumulator);
});
});
Thanks for any help.
From OP comment:
My intent is to query between 2 payment_at of one user and one type.
If that is the case, you'll need a GSI with
a partition (hash) that is a combination of user and type;
and a range (sort) that is the payment_at.
This will allow you to perform the access you require. The projection will depend on your use.
As it stands your primary table keys will allow you to select a range of payment_at for a given user. It might suffice to query that. Or it may be that you could use a sort key that is a compound of type and payment_at. The best choice really depends on your access patterns.
I need to sort a collection with multiple conditions. But, in between these conditions I need to modify the stream data.
Customer.class
import java.math.BigDecimal;
import java.util.Date;
public class Customer {
private int id;
private String name;
private long quantity;
private BigDecimal cost;
private Date lastPurchasedDate;
public Customer(int id, String name, long quantity, BigDecimal cost) {
this.id = id;
this.name = name;
this.quantity = quantity;
this.cost = cost;
}
// setters & getters are omitted for brevity.
}
Main.class
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
public class Main {
public static void main(String[] args) {
List<Customer> customers = Arrays.asList(new Customer(1, "A", 10, BigDecimal.valueOf(673.89)),new Customer(2, "B", 10, BigDecimal.valueOf(673.89)));
getCustomer(customers).ifPresent(c -> System.out.println(c.getId()));
}
private static Optional<Customer> getCustomer(List<Customer> customers) {
// #formatter:off
return customers.stream()
.max(
Comparator.comparing(Customer::getQuantity)
.thenComparing(Customer::getCost)
// here I need to check, If multiple customers are after
// comparing the cost, I have to update the lastPurchasedDate l
// attribute value & then sort it
.thenComparing(Customer::getLastPurchasedDate)
);
// #formatter:on
}
}
If there are multiple customers available after sorting by cost then I need to populate the lastPurchasedDate attribute value and then sort it by lastPurchasedDate.
Why I'm not populating the lastPurchasedDate data before ?
To get that information, I need run a query on db (To get the information, we are going to use a Table which will have millions of records). So it is a performance constraint & I want to avoid it. A very rare scenario of comparing by lastPurchasedDate needed. So I don't want to unnecessarily run this query for all the customers.
This is the solution I came up with...
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Optional;
public class Main {
public static void main(String[] args) {
List<Customer> customers = Arrays.asList(new Customer(1, "A", 10, BigDecimal.valueOf(673.89)),new Customer(2, "B", 10, BigDecimal.valueOf(673.89)));
getCustomer(customers).ifPresent(c -> System.out.println(c.getId()));
}
private static Optional<Customer> getCustomer(List<Customer> customers) {
// #formatter:off
return customers.stream()
.max(
Comparator.comparing(Customer::getQuantity)
.thenComparing(Customer::getCost)
.thenComparing(Main::updateLastPurchasedDate)
);
// #formatter:on
}
private static Date updateLastPurchasedDate(Customer c) {
Date lastPurchasedDate = customerRepository.getLastPurchasedDateById(c.getId());
if(lastPurchasedDate == null) {
return new Date(10000);
}
return lastPurchasedDate;
}
}
i need to update tow columns inside my table (Job this table is joint with two other tables employees and job-history)one of them is the primary key, but i get error, if someone can help.
package com.touati.org.model;
import java.io.Serializable;
import javax.persistence.*;
import java.math.BigDecimal;
import java.util.List;
/**
* The persistent class for the jobs database table.
*
*/
#Entity
#Table(name="jobs")
#NamedQuery(name="Job.findAll", query="SELECT j FROM Job j")
public class Job implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="JOB_ID")
private String jobId;
#Column(name="JOB_TITLE")
private String jobTitle;
#Column(name="MAX_SALARY")
private BigDecimal maxSalary;
#Column(name="MIN_SALARY")
private BigDecimal minSalary;
//bi-directional many-to-one association to Employee
#OneToMany(mappedBy="job")
private List<Employee> employees;
//bi-directional many-to-one association to JobHistory
#OneToMany(mappedBy="job")
private List<JobHistory> jobHistories;
public Job() {
}
public String getJobId() {
return this.jobId;
}
public void setJobId(String jobId) {
this.jobId = jobId;
}
public String getJobTitle() {
return this.jobTitle;
}
public void setJobTitle(String jobTitle) {
this.jobTitle = jobTitle;
}
public BigDecimal getMaxSalary() {
return this.maxSalary;
}
public void setMaxSalary(BigDecimal maxSalary) {
this.maxSalary = maxSalary;
}
public BigDecimal getMinSalary() {
return this.minSalary;
}
public void setMinSalary(BigDecimal minSalary) {
this.minSalary = minSalary;
}
public List<Employee> getEmployees() {
return this.employees;
}
public void setEmployees(List<Employee> employees) {
this.employees = employees;
}
public Employee addEmployee(Employee employee) {
getEmployees().add(employee);
employee.setJob(this);
return employee;
}
public Employee removeEmployee(Employee employee) {
getEmployees().remove(employee);
employee.setJob(null);
return employee;
}
public List<JobHistory> getJobHistories() {
return this.jobHistories;
}
public void setJobHistories(List<JobHistory> jobHistories) {
this.jobHistories = jobHistories;
}
public JobHistory addJobHistory(JobHistory jobHistory) {
getJobHistories().add(jobHistory);
jobHistory.setJob(this);
return jobHistory;
}
public JobHistory removeJobHistory(JobHistory jobHistory) {
getJobHistories().remove(jobHistory);
jobHistory.setJob(null);
return jobHistory;
}
}
my controller: here when i try to look for all job in the data base it works fine, also if i try to update juste the title of the job it works fine to but in case that i try to set a new primary key for the job table it gives me error here my controller.
package com.touati.org.model;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
#Controller // This means that this class is a Controller
#RequestMapping(path="/project") // This means URL's start with /demo (after Application path)
public class MainController {
#GetMapping(path="/job")
public #ResponseBody Iterable<Job> getAllJob() {
// This returns a JSON or XML with the users
return jobRepository.findAll();
}
#GetMapping(path="/job/{jobId}")
public #ResponseBody String getJob(#PathVariable String jobId) {
Job job = jobRepository.findOne(jobId);
try {
job.setJobTitle("manager");
job.setJobId("test1");
jobRepository.save(job);
}
catch (Exception ex) {
return "Error updating the job: " + ex.toString();
}
return "Job succesfully updated!";
}
}
i got this error,
Error updating the user: org.springframework.orm.jpa.JpaSystemException: identifier of an instance of com.touati.org.model.Job was altered from test to test1; nested exception is org.hibernate.HibernateException: identifier of an instance of com.touati.org.model.Job was altered from test to test1
Thank you for your help.
Primary key should never be changed. If you need to change primary key it means your design is bad. If you need to change JOB_ID often then create another column for your primary key like ID. Another possibility is to copy all attributes and create new record with new JOB_ID and then remove old one.
I have implemented a database inheritance of type "InheritanceType.JOINED".
I have extended CrudRepository interface to do CRUD operations.After implementing some unit tests. i figure out that save & update works perfectly, but delete not working at all. So, what's makes this happen ?
Here is the code Unite test :
#RunWith(SpringRunner.class)
#SpringBootTest
public class LineCommandRepoTest {
#Autowired
CommandRepository commandRepository;
#Autowired
ProduitRepository produitRepository;
#Autowired
LineCommandRepository lineCommandRepository;
public void update() {
LineCommande lc = lineCommandRepository.findOne(4);
lc.setQty(BigDecimal.valueOf(2000));
lc.setRemise(BigDecimal.valueOf(2000));
lc.setPrice(BigDecimal.valueOf(2000));
// lineCommandRepository.save(lc);
lineCommandRepository.save(lc);
LineCommande lc2 = lineCommandRepository.findOne(4);
Assert.assertTrue(lc.getPrice().equals(BigDecimal.valueOf(2000)));
}
public void insert() {
Commande commande = commandRepository.findOne(1);
Product p = produitRepository.findOne(1);
LineCommande lc = new LineCommande();
lc.setQty(BigDecimal.ONE);
lc.setPrice(BigDecimal.ONE);
lc.setRemise(BigDecimal.ONE);
lc.setCommande(commande);
lc.setProduct(p);
lineCommandRepository.save(lc);
Assert.assertTrue(lineCommandRepository.exists(lc.getIdLine()));
}
#Test
public void delete() {
lineCommandRepository.delete(4);
Assert.assertFalse(lineCommandRepository.exists(4));
}
}
Here is the code for the superclass:
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import org.springframework.data.annotation.AccessType;
import javax.persistence.*;
import java.math.BigDecimal;
#Entity
#Table(name = "linecpiece")
#Inheritance(strategy = InheritanceType.JOINED)
#AccessType(AccessType.Type.PROPERTY)
public abstract class LinePiece {
private int idLine;
private ObjectProperty<BigDecimal> qty = new SimpleObjectProperty<BigDecimal>(BigDecimal.ZERO);
private ObjectProperty<BigDecimal> price = new SimpleObjectProperty<BigDecimal>(BigDecimal.ZERO);
private ObjectProperty<BigDecimal> remise = new SimpleObjectProperty<BigDecimal>(BigDecimal.ZERO);
private IntegerProperty tva = new SimpleIntegerProperty();
private ObjectProperty<BigDecimal> subTotal = new SimpleObjectProperty<BigDecimal>(BigDecimal.ZERO);
// Getter and setter with annotations
}
Code for child class
package com.example.model.purchase;
import com.example.model.Product;
import javax.persistence.*;
#Entity
#Table(name = "linecommande")
#PrimaryKeyJoinColumn(name = "idlinepiece")
public class LineCommande extends LinePiece {
private Commande commande;
private Product product;
#ManyToOne
#JoinColumn(name = "idcommercialepiece")
public Commande getCommande() {
return commande;
}
public void setCommande(Commande commande) {
this.commande = commande;
}
#ManyToOne
#JoinColumn(name = "idproduct")
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
/*
public static Callback<LineCommande, Observable[]> extractor() {
return (LineCommande p) -> new Observable[]{p.qtyProperty(), p.priceProperty(), p.subTotalProperty()};
}*/
}
you need to call flush() after lineCommandRepository.delete(4);
flush() doesn't exist in CrudRepository , but it's presenet in EntityManager
Suppose, that we have such tables:
Table Users
iduser | password
Table Marks
id | iduser | mark | idtest
Table Tests
idtest | title
Query looks like this:
#GET
#Path("/{id}/marks")
#Produces(MediaType.APPLICATION_JSON)
public Object funkcja(#PathParam("id") Integer iduser) {
Query query = em.createQuery("select m,t from Marks m, Tests t where m.idusers=:iduser and m.idtest = t.idtests");
query.setParameter("iduser", id);
List<Object> results = (List<Object>)query.getResultList();
return results;
}
I have entity classes:
Marks , Users, Tests
What I should to do in order to join tables and send JSON type on web service and how to convert JSON to entity class because I would like to show in TableView.
Perhaps there are other simple ways?
Maybe map or JsonObject?
You seem to have multiple questions here; I think you need to break these down into separate questions.
For the "main" question, which is about JPA and how to join the entities, I would do that at the entity level, not at the query level. I.e. I think I would have entity classes like:
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name="Tests")
public class Test {
#Id
#Column(name="idtest")
private int id ;
private String title ;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
#Override
public boolean equals(Object other) {
if (other instanceof Test) {
return Objects.equals(title, ((Test)other).title);
} else return false ;
}
#Override
public int hashCode() {
return Objects.hash(title);
}
}
and then the Mark entity can use a #ManyToOne annotation to reference the actual Test object (not its id):
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
#Entity
#Table(name="Marks")
public class Mark {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id ;
#ManyToOne
#JoinColumn(name="idtest")
private Test test ;
// You probably don't want a reference to User here, as the User class
// encapsulates the password, which you don't want to throw back and
// forward across the network unnecessarily. But if the User class
// instead had a user name etc you wanted, you could use the same
// #ManyToOne technique to reference a User object here if you needed.
#Column(name="iduser")
private int userId ;
private int mark ;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public Test getTest() {
return test;
}
public void setTest(Test test) {
this.test = test;
}
public int getMark() {
return mark;
}
public void setMark(int mark) {
this.mark = mark;
}
#Override
public boolean equals(Object obj) {
if (obj instanceof Mark) {
Mark other = (Mark)obj ;
return Objects.equals(userId, other.userId)
&& Objects.equals(test, other.test)
&& mark == other.mark ;
} else return false ;
}
#Override
public int hashCode() {
return Objects.hash(userId, test, mark);
}
}
Now your query looks like
TypedQuery<Mark> query = em.createQuery("select m from Mark m where m.userId=:userid");
query.setParameter("userid", iduser);
List<Mark> results = query.getResultList();
and the Mark instances in the list already have all the data you need:
for (Mark mark : results) {
System.out.println(mark.getTest().getTitle() + ": " + mark.getMark());
}
For the remaining questions:
Assuming you have a server set up with a JAX-RS implementation (e.g. Jersey) the code snippet you showed (modified with the new query) should generate JSON output. (You can use a developer tool such as Firefox REST client to view the JSON output by specifying the appropriate URL and request headers, and viewing the response.)
On the client (JavaFX) side you can use Jersey's client library (or maybe Apache HttpComponent library) to create the web requests easily, and a library such as GSON or Jackson for mapping the JSON content you get back to a Java object for display in the TableView.
I recommend trying this and asking specific questions about the remaining pieces if you get stuck.