I'm using JPA 2.0, more precisely Eclipselink. Here's my problem:
I have an entity that has a property like "isPaid". that property is the result of some calculations the entity performs with some of its other fields. since this is derived from other fields, the property does not have a setter method.
As an example, the getter is something like this:
public boolean isPaid() {
return this.totalAmount - this.amountPaid == 0;
}
that's just an example. The thing is, I want this property to be calculated and persisted, so i can do a jpql query like:
SELECT d FROM Debt d WHERE d.isPaid = true
Is this possible? Is there any workaround for this?.
I don't want to retrieve all entities to call this method and then filter those that return true.
Here are a couple of options:
1) Create a jpql query that directly does what you need:
select d from Debt d where (d.totalAmount - d.amountPaid) = 0
The benefits of the approach is that it is simple and will always work. The downside is that your query has to understand how the paid logic was calculated.
2) Create a persisted paid value that stores the calculated value:
#Basic
private boolean paid;
public boolean isPaid() {
return this.paid;
}
private void updateCalculations() {
this.paid = (this.totalAmount - this.amountPaid == 0);
}
// using int as example here
public void setTotalAmount(int totalAmount) {
this.totalAmount = totalAmount;
updateCalculations();
}
public void setAmountPaid(int amountPaid) {
this.amountPaid = amountPaid;
updateCalculations();
}
The benefit of this approach is that you will be able to create a jpql query that directly checks for the boolean value, i.e.,
select d from Debt d where d.paid = true;
Obviously, the downside to the approach is that you need to make sure to recalculate the value anytime you update the values. However, this can be alleviated if you only calculate it on access. Meaning that in your isPaid() method, you calculate the value, assign it to the paid attribute and then return the value. If you decide to go with this approach, you will need to add a #PrePersist and #PreUpdate method that performs the paid calculation and updates the paid attribute prior to the bean being persisted to the datastore (makes sure that the paid value is always covered.
If you use JPA annotations on your attributes themselves, you can have a getter without a setter and still be able to correctly retrieve and store the values in the database.
Seen this: Mapping calculated properties with JPA ?
Basically you need a setter one way or the other in order to make JPA happy.
Related
Currently we have a class that looks something like that (depersonalised and non-relevant parts removed):
#Entity
#Table(name = "MAIN_TABLE")
public class MainTable extends AbstractTable {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "mainTable")
#OrderBy("CREATED_ON DESC")
private Set<MainTableState> states;
...
public MainTableState getActiveState(){
if(this.states == null || this.states.isEmpty()){
return null;
}
MainTableState latest = states.iterator().next();
// The reason we use this for-loop, even though we have the #OrderBy annotation,
// Is because we can later add states to this list, which aren't automatically ordered
for(MainTableState state : states){
if(state.getCreatedOn() != null && latest.getCreatedOn() != null &&
state.getCreatedOn().after(latest.getCreatedOn()){
latest = state;
}
}
return latest;
}
...
}
So currently it will retrieve all MainTableStates from the DB by default, and if we need the activeState we use the for-loop method. Obviously this is pretty bad for performance. Currently we don't use this list at all (the purpose was to have a history of states, but this has been postponed to the future), but we do use the getActiveState() method quite a bit, mostly to show a String inside of the MainTableState-class in the UI.
In addition, even if we would always use a TreeSet and keep it sorted so we won't need the loop but only need states.iterator().next() instead, it will still initialize the list of states. With some heavy performance testing we had more than 1 million MainTableState-instances when it crashed with an java.lang.OutOfMemoryError: GC overhead limit exceeded.
So, we want to change it to the following instead:
#Entity
#Table(name = "MAIN_TABLE")
public class MainTable extends AbstractEntity {
#???
private MainTableState activeState;
...
public MainTableStates getActiveState(){
return activeState;
}
...
}
So, my question, what should I put at the #??? to accomplish this? I'm assuming I need the #Formula or something similar, but how can I say to hibernate it should return a MainTableState object? I've seen #Formula being used with MAX for a date, but that was to get that date-property, not get an entire object based on that max date.
After #user2447161's suggestion I've used a #Where-annotation, which does indeed help to reduce the Collection size to 1 (sometimes), but I have two more related questions:
How to use #OnToMany and #Where but get a single object, instead of a list of objects of size one? Is this even possible? Here in a answer from December 2010 it is stated it isn't. Has this been fixed somewhere in the last six years?
How to deal with the random alias in the where clause? I could do something like this:
#OneToMany(fetch = FetchType.LAZY, mappedBy = "mainTable")
#Where(clause = "CREATED_ON = (SELECT MAX(mts.CREATED_ON) FROM MAIN_TABLE_STATES mts WHERE mts.FK_MAIN_ID = ???.MAIN_ID)")
private Set states; // TODO Get single object instead of collection with size 1
The problem with is that ??? is a random alias generated by hibernate (sometimes it's this_, sometimes it's something along the lines of mainTable_1_, etc.). How to set this alias for the entire query to the DB to use it here? I also tried MAIN_TABLE.MAIN_ID instead which doesn't work, and with no alias it also doesn't work because it uses the MainTableState-alias instead of MainTable-alias (like this below).
from
MAIN_TABLE this_
left outer join
MAIN_TABLE_STATUSES mainstat2_
on this_.main_id=mainstat2_.fk_main_id
and (
mainstat2_.created_on = (
SELECT
MAX(mts.created_on)
FROM
MAIN_TABLE_STATUSES mts
WHERE
-- mainstat2_.main_id should be this_.main_id instead here:
mts.fk_main_id = mainstat2_.main_id
)
)
Well, regarding your question #2, as it looks like you need a quick solution with minimal impact in your existing code, this may be acceptable: you can use an Interceptor to deal with the alias and generate the right sql statement. Do this:
use a unique string as alias placeholder in your #Where clause, for instance:
...WHERE mts.FK_MAIN_ID = ${MAIN_TABLE_ALIAS}.MAIN_ID...
if your application doesn't have one yet, create an Interceptor class extending EmptyInterceptor and configure it as a SessionFactory interceptor
override the onPrepareStatement method to replace the placeholder with the alias found after 'from MAIN_TABLE' with something like this:
public String onPrepareStatement(String sql) {
String modifiedSql = sql;
if (sql.contains("${MAIN_TABLE_ALIAS}")) {
String mainTableAlias = findMainTableAlias(sql);
modifiedSql = sql.replace("${MAIN_TABLE_ALIAS}", mainTableAlias);
}
return modifiedSql;
}
Be aware that this method will be called for every sql statement that hibernate generates in your application.
Additionaly, your #Where clause only works properly when a join is used, so you should set the fetch mode explicitly adding
#Fetch(FetchMode.JOIN)
to the states property to avoid that hibernate may use the select mode.
I'm trying to map EnumSet into a single value as integer via Hibernate.
I have implemented AttributeConverter:
public class RolesToIntConverter implements AttributeConverter<Set<Roles>, Integer> {
#Override
public Integer convertToDatabaseColumn(Set<Roles> attribute) {
return Roles.encode(attribute);
}
#Override
public Set<Roles> convertToEntityAttribute(Integer dbData) {
return Roles.decode(dbData);
}
}
As well as new SqlDialect:
public class LocalSqlDialect extends MySQL5Dialect {
public LocalSqlDialect() {
super();
registerFunction("bitwise_and", new SQLFunctionTemplate(IntegerType.INSTANCE, "(?1 & ?2)"));
}
}
Then I call it like this:
public Collection<PersonsEntity> getAll(Roles roles) {
Query q = getEntityManager().createQuery("SELECT s FROM PersonsEntity AS s WHERE ( bitwise_and(s.roles,:roles) <> 0 )");
q.setParameter("roles", EnumSet.of(roles));
List<PersonsEntity> result = (List<PersonsEntity>) q.getResultList();
return result;
}
This causes several issues:
ClassCastException, because for some reason, it passes set to the AttributeConverter per each element
I tried changing type from Set<Roles> to Object (at the converter), and then using instanceof I checked whether it is a single object or a set and parsed accordingly. After that I found out, that while calling s.roles = :roles worked fine, calling the registered bitwise function did not even call the AttributeConverter
ResultSet exception, because after calling bitwise function and using as input Set with two values, it actually puts ?, ? into the query instead of calling AttributeParser, which should merge it into a single number
The question is, what em I doing wrong? Or do you know a better solution to a problem: map EnumSet into a single database column, while being able to assign multiple roles to one entity.
For example I have value 1 for user, value 2 for manager, value 4 for admin, etc.. I want a particular person to be a user and a manager at the same time, which would mean value 3 (1 | 2) and then I want to find him when searching for user only (resp. manager only) via number 1 (resp. 2) - which suggests bitwise and.
Thank you in advance for any response!
My class is not Entity there is code fragment
#SequenceGenerator(name="seqUniqueKeyGenerator",sequenceName="SEQ_UNIQUE_KEY",allocationSize=1)
#GeneratedValue(strategy=GenerationType.SEQUENCE,generator="seqUniqueKeyGenerator")
#Id
private Integer sequenceId;
public Integer getSequenceId() {
return sequenceId;
}
public void setSequenceId(Integer sequenceId) {
this.sequenceId = sequenceId;
}
public static void main(String[] args) {
UniqueKeyGenerator uniqueKeyGenerator = new UniqueKeyGenerator();
System.out.println(uniqueKeyGenerator.getSequenceId());
}
I want retrieve nextVal like this, is it possible?
You can consume nextVal as mentioned in this thread but you have to consider it is consumed by means of a SQL sentence, which means this is a solution coupled to database.
I don't know a way to consume nextVal in such way you are asking above.
We all know the default behaviour of Hibernate when using #SequenceGenerator - it increases real database sequence by one, multiple this value by 50 (default allocationSize value) - and then uses this value as entity ID.
From G. Demecki
This means that the ID id not generated in your Java-Program. It is created in your database. So you can't read it before it is acutally generated in the database. You can guess it using the formula described by G.Demecki but that is certainly not the way to go.
If you want the id of an entity just save it and read the id from the return value of save what should be the saved entity itself.
Based on object-oriented approach, I write the following entities:
#Entity
public class Customer {
#Id
private String id;
#OneToMany
private List<Order> orders;
public BigDecimal getTotal() {
// iterate over orders and sum the total
BigDecimal total = BigDecimal.ZERO;
for (Order o: orders) {
total = total.add(o.getTotal());
}
return total;
}
... // getter & setter
}
#Entity
public class Order {
#Id
private String id;
private BigDecimal total;
...
}
I realized that when calling getTotal() method for a Customer, Hibernate will issue a SELECT * FROM Order query to retrieve all Orders. The number of Order will surely increase as years passed. I believe a SELECT SUM(o.total) FROM Order o will give a better performance, CMIIMW. I just don't know where should I put the query? Rules of object oriented design suggest that getTotal() should be part of Customer, but the framework I'm using (Spring Transaction) doesn't allow transaction in domain objects.
In the first case, select * from orders.
You are just getting the list of orders and you need to calculate the sum in Server side code with iterating over the orders.
In the second case, select sum(o.total) from orders where order.customer_ id = 1234;
Database is doing the calculation for you. In terms of performance also,
this is better.
Why the database needs to delegate to some upper layer, when It can do it.
So I suggest you with the second case only.
As per OO, It might suggest to encapsulate both properties and related methods.
But its a Domain Class, which gets directly mapped to fields in Database.
To separate Data Access Logic, We can have a separate layer i.e DAO and put the desired logic in it.
So I have a class with three fields that maps to a table using hibernate
Class Widget
{
String field1;
String field2;
String field3;
}
On application startup a number of instances these widgets will be added to the database from an external files, but when I exit the application I need to know which (if any) of these fields have been changed by the user since the application was started, so the changes can be saved back to the files. I also need to store the original value for logging purposes.
I can't work whether I need a status field in the table or whether there is already a way of doing this using Hibernate/Database.
EDIT:A good solution to the program was given below . however the main reason I am using Hibernate is to reduce memory consumption so storing the original values when changed is not a good solution for me , I want everthing stored in the database. So I have create this new question How do I store a copy of each entity I add to database in Hibernate
Given an entity like the following you can track changes on one of it's field (while preserving its original value too).
#Entity
#Table(schema = "test", name = "test")
public final class Test {
private static final int ORIGINAL = 0;
private static final int CURRENT = 1;
private Integer id;
// holds the original and current state of the field
private final AtomicReferenceArray<String> field = new AtomicReferenceArray<>(2);
#Id
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
#Transient
public String getOriginalField() {
return field.get(ORIGINAL);
}
#Basic
public String getField() {
return field.get(CURRENT);
}
public void setField(String field) {
this.field.compareAndSet(ORIGINAL, null, field);
this.field.set(CURRENT, field);
}
#PreUpdate
public void preUpdate() {
System.out.format("Original: %s, New: %s\n", getOriginalField(), getField());
}
...
}
If there is a single row in a database like this:
id: 1
field: a
version: 2011-12-02 11:24:00
before the field gets updated (say, from a to b) you'll get the following output.
Original: d, New: b
The original value gets preserved even if the the entity is updated multiple times and both state can be accessed through the corresponding getters (getField and getOriginalField—you can get more creative than me in the naming :).
This way, you can spare yourself from creating version columns in your database and also can hide the implementation details from clients.
Instead of an AtomicReferenceArray you could use arrays, lists, etc, to track all changes like this way.
The #PreUpdate isn't necessary of course, but this way you can be notified of changes in the entity's state and atomically save the updated fields into file. There more annotations like these: see the documentation for javax.persistence for other annotation types.
If you are using MySql then you can get table's last update time from information_schema database like
SELECT UPDATE_TIME FROM `information_schema`.`tables`
WHERE TABLE_SCHEMA = 'dbName' AND TABLE_NAME = 'tableName'
Or else simple solution will be to add a column for update time stamp. By this you can even monitor which particular row has been updated.
If you need to synchronize with files as soon as you save into database, You can use the Hibernate event mechanism to intercept any save to database and save it to file, here's a sample doing that.