spring boot - calculated field - spring

So, I have an entity, that has field start_date (java.util.Date is the type).
I want to have another field, that would automatically populate with integer that corresponds to day of week (as a number 1 for sunday, 2 for monday, etc.) of starting date.
Here's fragment of my entity:
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#DateTimeFormat(pattern = "yyyy-MM-dd")
#Temporal(TemporalType.DATE)
private Date start_date;
I've tried to add the calculated field in the following way:
#Column(name = "weekday")
#Formula("(select dayofweek(l.start_date) from Lesson l where l.id = id)")
private Integer weekDay;
However, when looking at the Lesson table in H2 console, there's no such column as "weekday"
I also tried other option - without #Formula annotation and with a setter that takes start_date parameter, but I guess this setter is never called, since the column "weekday" is populated with null.
Here's the setter I've tried to use as an alternative solution:
public void setWeekDay(Date start_date) {
Calendar c = Calendar.getInstance();
c.setTime(start_date);
this.weekDay = c.get(Calendar.DAY_OF_WEEK);
}
It's obvious that I'm missing something here, it's probably because I'm still learning Spring boot...
To sum up - I want to have a column in table Lesson, that is calculated from another column of the same table.

#Formula means your field to be calculated by your rules. This entity field is not column in db. This field is calculating for each entity in loading time by specified rule.
If annotation #Column(name = "weekday") would work near #Formula you would be really confused if you expect in loaded entity same value as in DB but here is calculated one and different (inconsistent situation).
If you want save here value from the Lesson table you should remove #Formula and use #EntityListeners({YourEntityJpaCallbacksListener.class}) In spring bean YourEntityJpaCallbacksListener you can define methods marked with #PreUpdate or #PrePersist and use correspond operations to set calculated value into weekday.
for example:
#EntityListeners({YourEntityJpaCallbacksListener.class})
#Entity
public class YourEntity{
// your code
}
#Component
public class YourEntityJpaCallbacksListener {
#Autowired
private LessonRepository lessonRepository;
#PreUpdate
void preUpdate(YourEntity yourEntity) {
if (recurrentRuleRepository.exists(yourEntity.getId())) {
Integer weekDay = lessonRepository.findOne(yourEntity.getId());
yourEntity.setWeekDay(weekDay);
}
}
}

Ok, so I think I have managed to solve this.
I have removed the #Formula annotation
I have created a method that calculates the day of the week and writes it to weekDay.
public void calculateDayOfWeek(){
Calendar c = Calendar.getInstance();
c.setTime(start_date);
this.weekDay = c.get(Calendar.DAY_OF_WEEK);
}
And I put it inside start_date setter:
public void setStart_date(Date start_date) {
this.start_date = start_date;
calculateDayOfWeek();
}
So now, evertime the start_date is created or updated, it updates (or creates) the value of weekDay.

Related

Spring JPA Equal conditon with derived query for LocalDate not working

I have a Oracle DB with type of DATE my Entiy class have LocalDate parameter as shown in here
#Entity
#Table(name = "CONTRACT")
public class ContractEntity {
private Long id;
private LocalDate contractStartDate;
private LocalDate contractEndDate;
...
For the testing I created derived query to find Entity by contractStartDate
public interface ContractRepository extends JpaRepository<ContractEntity, Long>, JpaSpecificationExecutor<ContractEntity> {
ContractEntity getContractEntityByContractStartDateIs(LocalDate date)
}
Then I created Two Tests as follows;
#DisplayName("Should get Exact Date Entity")
#Test
void test1(){
LocalDate date=LocalDate.of(2021,03,9);
ContractEntity entity= searchService.getByDate(date); // getByDate is mapped to getContractEntityByContractStartDateIs() in service layer
assertEquals(date,entity.getContractStartDate());
}
#DisplayName("Should match ID:1 Entity's Date with Local Date")
#Test
void test2(){
ContractEntity entity=searchService.getById(1l).get();
assertEquals(1l,entity.getId());
LocalDate date=LocalDate.of(2021,03,9);
assertEquals(date,entity.getContractStartDate());
}
}
My problem is test1 fails with null error which means equality condition fails to give existence of that Entity even though test2 passed which date is equal to id:1 entity's Start-date
p.s: I have set Contract Table's ID:1 Contract Start Date value to 2021,03,9 as shown in image here
So what would be the reason for this Why test1 fails to get equal entity and why test2 pass that assert equity of date is true
LocalDate doesn't contain a time component, but the data type you used in the database does and it is not 0 for the data in the database.
In order to make the comparison (which happens in the database) the LocalDate gets converted with a date + time where the time is probably 0, which is not equal to the date+times present in the database.
When the entity gets loaded the time part of the database value gets dropped and the result is equal to your LocalDate value.

Spring entity dynamically calculated field with parameter from native query

I have a somewhat complex entity like following (Notice the super-class with many more fields):
public class Question extends Entry {
#OneToMany(orphanRemoval = true, mappedBy = "question")
#JsonManagedReference
private List<Answer> answers = new ArrayList<>();
private Long viewCount = 0L;
private Category category;
#OneToMany(mappedBy = "question", fetch = FetchType.LAZY,
cascade = CascadeType.ALL, orphanRemoval = true)
private List<QuestionTranslation> translations = new ArrayList<>();
#Transient
private double distance;
}
distance should be calculated from the DB when retrieving the result set from a native query.
E.g.
SELECT q.*, ST_Distance_Sphere(cast(q.location as geometry), ST_MakePoint(cast(?1 as double precision), cast(?2 as double precision))) as distance from question q
I cannot use #Formula to annotate my field distance since the query has to take parameters.
How can I map the field distance from the SQL query result to my entity field distance while leaving all the other mappings to be done by Hibernate?
Edit
Based on #gmotux suggestion I created a wrapper entity.
#Entity
#SqlResultSetMapping(
name="MappingQ",
entities={
#EntityResult(
entityClass = QuestionWithDistance.class,
fields={
#FieldResult(name="distance",column="distance"),
#FieldResult(name="question",column="question")})})
public class QuestionWithDistance{
#Id
#GeneratedValue
private String id;
#OneToOne
private Question question;
private double distance;
}
Query
Query query = entityManager.createNativeQuery("SELECT q.*, 222.22 as distance from question q", "MappingQ");
But it always fails with
org.postgresql.util.PSQLException: The column name id1_15_0_ was not found in this ResultSet.
Since you need extra parameters to calculate your field, you indeed cannot use #Formula, or even a getter to calculate the field.
Unfortunately for your case the only thing that comes to mind, assuming you are using an EntityManager based configuration for Hibernate, is leveraging its #PostLoad event listener, which you can use for calculating field values upon entity loading, like :
public class Question extends Entry {
#PostLoad
private void postLoad() {
this.distance = DistanceCalculator.calculateDistance(Double param1,Double param2);
//other calculations
}
}
That of-course is only a workaround and it means that you must have a static method somewhere execute native queries.
I would suggest detaching the "distance" notion from your Question entity, if possible in your requirements and calculate it, when required, with either a native SQL function call or a service method.

Add data to database from Controller, different methods but same row

I have an entity model, for simplification purposes let's say it looks like this :
public class Results {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private Long firstUser;
private Long secondUser;
private Double average;
private Double median;
private Double score;
}
This is my ResultsService Implementation:
public class ResultsServiceImpl implements ResultsService{
#Autowired
private CalculateDataRepository calculateDataRepository;;
#Autowired
private ResultsService resultsService;
Results results=new Results();
public void Average(Long id1, Long id2){
UserData firstClient = calculateDataRepository.findOne(id1);
userData secondClient = calculateDataRepository.findOne(id2);
clientId = firstClient.getClient().getId();
secondId = secondClient.getClient().getId();
Double average=(firstClient.getA()+secondClient.getA())/2;
results.setAverage(average);
}
public void Score(Long id1, Long id2){
SurveyData firstClient = surveyDataRepository.findOne(id1);
SurveyData secondClient = surveyDataRepository.findOne(id2);
clientId = firstClient.getClient().getId();
secondId = secondClient.getClient().getId();
Double average=(firstClient.getB()+secondClient.getB());
results.setScore(average);
results.setFirstUser(clientId );
results.setSecondUser(secondId );
resultsService.save(results);
}
....
I tried declaring Results results=new Results(); inside every method, but when I save them they get saved in different rows, instead of the same one.
How do I hold the reference so that when I call the setter of a field in one function, it's in the same row as the setter of a field in the other function.
To keep the problem focused, I tried to avoid showing the implementation of calculateDataRepository which is just the repository of an entity where some results are saved for different users.
The Results Method has no foreign field reference nor a reference from somewhere else, as there are fields firstUser and secondUser which I set from one of the methods;
Thank you.
Edit:
Results results=resultsService.findByFirstUserAndSecondUser(clientId, secondId);
if(results==null) {
results= new Results();
// Store to db ?
}
results.setAverage();
resultsService.save(results);
Actually you need a method in ResultsRepository
Results findByFirstAndSecond(Long first, Long second);
In the each Average and Score methods (BTW Java naming convention requires to have method names start from lowercase letter) call the findByFirstAndSecond(id1, id2)
If the method returns null (no such result) create a new instance and save in the DB (INSERT). If some Results is returned store the info there and save changes in DB (UPDATE).

Initialize field with current year and assigned ID

I've got an entity that I am persisting. Its ID is automatically assigned when storing it into the database via Spring Repository.
In the same entity, I have a field build from the Id and the current year: "<current_year>-<id>".
In a method annotated with #PrePersist, the ID has not been assigned yet, so I wrote some code in a #PostPersist method:
#PostPersist
protected void setupOrderNumber() {
this.orderNumber = Calendar.getInstance().get(Calendar.YEAR) + "-" + id;
}
This code does not store the orderNumber into the database, because the entity was stored already.
How can I achieve such a result with JPA directly within the entity?
If not possible with JPA, I could use Hibernate with a #Formula annotation, but I am not sure how to write it: #Formula("extract(year from current_date) + '-' + id") does not seem to work.
As you've already noticed: In #PrePersist a generated ID is not available - just because the ID is set afterwards when persisting into the database. And no changes made in #PostPersist are persisted, just because the persist has already taken place...
You can use a #Formula, as long you don't need the value in the database. But I wouldn't use extract(year from current_date) - as this would change the orderNumber when the year changes - what is different to your experiment with #PostPersist.
Instead use a year field, which you initialize in #PrePersist and reference that one in your formula:
#Entity
public class MyEntity {
#Id
#GeneratedValue(...)
private Long id;
private int year;
#Formula("concat(id, '-', year)")
private String orderNumber;
#PrePersist
private void prePersist() {
this.year = Calendar.getInstance().get(Calendar.YEAR);
}
#PostPersist
private void postPersist() {
this.orderNumber = id + "-" + year;
}
}
I initialize the orderNumber in postPersist() as well, to have a valid value immediately after EntityManager.persist().

JPA Why only this #ManyToOne returns null?

UPDATE
I find something interesting
When I run JPQL like this
SELECT s FROM STUDENTS s WHERE s.community=:community
then this issue happens, but this query runs fine and returns all necessary fields
SELECT s FROM STUDENTS s WHERE s.id=:id
meaning if there is an inner join with its child field, then somehow the value of the other field get missing. I got both native query and both returns all necessary field values. Must be openjpa dismiss the school fields some how when native query returns
Yes, Community and School both may map to another entity, but how come that could cause this strange behavior? I am kind of mad with OpenJPA
I have spent hours to fix this strange issue
there is a class contains several #ManyToOne relation
public class Student{
// Relationships
#NotNull
#ManyToOne(fetch=FetchType.EAGER)
private Teacher teacher;
#ManyToOne(fetch=FetchType.EAGER)
#Column(name = "SCHOOL_ID")
private School school;
#ManyToOne(fetch=FetchType.EAGER)
private Club club;
#ManyToOne(fetch=FetchType.EAGER)
private Bus bus;
....
}
Each of the many side has a definition like this(Only give the School entity as it has the issue):
public class School implements Institution{
// Relationships
#OneToMany(mappedBy="school")
private List<Student> students= new ArrayList<Student>();
....
}
The problem here is when I try to get All students for a certain age:
SELECT s FROM STUDENTS s WHERE s.age=:age
I can clearly see the all other #ManyToOne fields' value ONLY EXCEPT school, and in Oracle database the school_id field clearly stored with correct data which map to the correct entry in School table
So what could be the possible reason for this situation?
I am using Spring MVC3 + Openjpa + Roo
Your school mapping should use a #JoinColumn annotation (not a #Column annotation):
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name = "SCHOOL_ID")
private School school;

Resources