Hibernate handle long 0 value instead of NULL in ManyToOne relations - java

I use Hibernate to access to a legacy DB. For some tables the parent-child reference integrity is not enforced, and the long 0 value is used instead of NULL for some "parent" columns in child tables to denote "no parent".
I still want to use these relations in #ManyToOne and #OneToMany fields, but get EntityNotFound error since the 0 value does not correspond to any record in master table.
What are my options?

Use the NotFound annotation:
#NotFound(action = NotFoundAction.IGNORE)
See http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html_single/#mapping-declaration-manytoone

Instead of the #JoinColumn could be used #JoinFormula. Like this
#JoinFormula(value="CASE the0isNullColumn"
+ " WHEN 0"
+ " THEN NULL"
+ " ELSE the0isNullColumn"
+ " END")
The expression means we check the column and if it's 0 return NULL. Then hibernate doesn't search for the related entity.

You can map it to java.lang.Long which default value is null. Or you can use a #PostLoad and null it if 0. You can also use a #Formula and ignore 0.
The #Formula as written in their documentation can be used to join conditions.
Since I don't know your data model providing a valid example is tricky. Try with:
id_fk is not null or id_fk <> 0
block.
If it does not suit your needs you can write you own Query loader
If you are using some sort of logging enable the show_sql property. And add to your config the org.hibernate.sql DEBUG.

Related

What is Empty in JPA?

I create a customer table and for the column called as loan
I pass null for 1 row
I pass '' for another row
and when I execute this query
SELECT c FROM Customer c WHERE c.loans IS EMPTY
I get nothing.
The IS EMPTY operator is the logical equivalent of IS NULL, but for collections.
Queries can use IS EMPTY operator or IS NOT EMPTY to check whether a collection association path resolves to an empty collection or has at least one value.
We can use the EMPTY to check if a property is empty.
The following JPQL shows how to use EMPTY to get employee withno projects.
Query unassignedQuery =
em.createQuery("SELECT e " +
"FROM Employee e " +
"WHERE e.projects IS EMPTY");
According to the JPA 2.1 spec:
If there are no associated entities for a multi-valued relationship of
an entity fetched from the database, the persistence provider is
responsible for returning an empty collection as the value of the
relationship.
Reference

Hibernate is making extra SQL statement with #ManyToOne and #Lazy fetching object

I would like someone to explain me why Hibernate is making one extra SQL statement in my straight forward case. Basically i have this object:
#Entity
class ConfigurationTechLog (
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long?,
val configurationId: Long,
val type: String,
val value: String?
) {
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "configurationId", insertable = false, updatable = false)
val configuration: Configuration? = null
}
So as you can see, nothing special there. And when i execute this query :
#Query(value = "SELECT c FROM ConfigurationTechLog c where c.id = 10")
fun findById10() : Set<ConfigurationTechLog>
In my console i see this:
Hibernate:
/* SELECT
c
FROM
ConfigurationTechLog c
where
c.id = 10 */ select
configurat0_.id as id1_2_,
configurat0_.configuration_id as configur2_2_,
configurat0_.type as type3_2_,
configurat0_.value as value4_2_
from
configuration_tech_log configurat0_
where
configurat0_.id=10
Hibernate:
select
configurat0_.id as id1_0_0_,
configurat0_.branch_code as branch_c2_0_0_,
configurat0_.country as country3_0_0_,
configurat0_.merchant_name as merchant4_0_0_,
configurat0_.merchant_number as merchant5_0_0_,
configurat0_.org as org6_0_0_,
configurat0_.outlet_id as outlet_i7_0_0_,
configurat0_.platform_merchant_account_name as platform8_0_0_,
configurat0_.store_type as store_ty9_0_0_,
configurat0_.terminal_count as termina10_0_0_
from
configuration configurat0_
where
configurat0_.id=?
Can someone please explain me, what is happening here ? From where this second query is coming from ?
I assume you are using Kotlin data class. The kotlin data class would generate toString, hashCode and equals methods utilizing all the member fields. So if you are using the returned values in your code in a way that results in calling of any of these method may cause this issue.
BTW, using Kotlin data claases is against the basic requirements for JPA Entity as data classes are final classes having final members.
In order to make an association lazy, Hibernate has to create a proxy instance instead of using the real object, i.e. it needs to create an instance of dynamically generated subclass of the association class.
Since in Kotlin all classes are final by default, Hibernate cannot subclass it so it has to create the real object and initialize the association right away. In order to verify this, try declaring the Configuration class as open.
To solve this without the need to explicitly declare all entities open, it is easier to do it via the kotlin-allopen compiler plugin.
This Link can be useful for understand what kind (common) problem is that N + 1 Problem
Let me give you an example:
I have three Courses and each of them have Students related.
I would like to perform a "SELECT * FROM Courses". This is the first query that i want (+ 1) but Hibernate in background, in order to get details about Students for each Course that select * given to us, will execute three more queries, one for each course (N, there are three Course coming from select *). In the end i will see 4 queries into Hibernate Logs
Considering the example before, probably this is what happen in your case: You execute the first query that you want, getting Configuration Id = 10 but after, Hibernate, will take the entity related to this Configuration, then a new query is executed to get this related entity.
This problem should be related in specific to Relationships (of course) and LAZY Fetch. This is not a problem that you have caused but is an Hibernate Performance Issue with LAZY Fetch, consider it a sort of bug or a default behaviour
To solve this kind of problem, i don't know if will be in your case but ... i know three ways:
EAGER Fetch Type (but not the most good option)
Query with JOIN FETCH between Courses and Students
Creating EntityGraph Object that rappresent the Course and SubGraph that rappresent Students and is added to EntityGraph
Looking at your question, it seems like an expected behavior.
Since you've set up configuration to fetch lazily with #ManyToOne(fetch = FetchType.LAZY), the first sql just queries the other variables. When you try to access the configuration object, hibernate queries the db again. That's what lazy fetching is. If you'd like Hibernate to use joins and fetch all values at once, try setting #ManyToOne(fetch = FetchType.EAGER).

JPA JoinColumn Annotation

In my JPA class, I have this Annotation and the syntax I am not able to understand
#JoinColumns({
#JoinColumn(name="RES_ID", referencedColumnName="ACCT_ID"),
#JoinColumn(name="DELETED", referencedColumnName="'N'")
})
protected Account account;
The first line is ok: The current class has column in db (RES_ID) which joins with Account which has a column ACCT_ID
But the second line says :
#JoinColumn(name="DELETED", referencedColumnName="'N'")
Now both these tables have a column called DELETED. Is that a shorthand way of saying that join the two tables when both these tables have DELETED = 'N'?
Because the documentation says that referencedColumn should contain a columnName. here it is containing a value = N
Let me guess, you're using OpenJPA? This is surely not a specified JPA feature, but OpenJPA has such a feature called constant joins in its Non-Standard Joins.

Hibernate Many-To-One Foreign Key Default 0

I have a table where the the parent object has an optional many-to-one relationship. The problem is that the table is setup to default the fkey column to 0.
When selecting, using fetch="join", etc-- the default of 0 on the fkey is being used to try over and over to select from another table for the ID 0. Of course this doesn't exist, but how can I tell Hibernate to treat a value of 0 to be the same as NULL-- to not cycle through 20+ times in fetching a relationship which doesn't exist?
<many-to-one name="device" lazy="false" class="Device" not-null="true" access="field" cascade="none" not-found="ignore">
<column name="DEVICEID" default="0" not-null="false"/>
There are two ways of doing this, the way that can get ugly performance-wise and the way that is painful and awkward.
The potentially ugly way is done on the ToOne end. Using Hibernate Annotations it would be:
#Entity
public class Foo
{
...
#ManyToOne
#JoinColumn( name = "DEVICEID" )
#NotFound( action = NotFoundAction.IGNORE )
private Device device;
...
}
Unfortunately, this forces a preemptive database hit (no lazy loading) because device can be null, and if Hibernate created a lazy Device then "device == null" would never be true.
The other way involves creating a custom UserType that intercepts requests for the ID 0 and returns null for them, and then assigning that to the primary key of Device with #Type. This forces the 0 ~ null interpretation on everyone with a foreign key into Device.
I was able to fix this by creating an id-long type which extends the built in Long type, but if the id returned from SQL was 0, return null instead. This kept the allowance of default 0s in our DB while getting hibernate to stop doing lazy fetches.
public class IdentifierLongType extends LongType implements IdentifierType {
#Override
public Object get(ResultSet rs, String name) throws SQLException {
long i = rs.getLong(name);
if (i == 0) {
return null;
} else {
return Long.valueOf(i);
}
}
}
The reason for enforcing explicit default 0 is that Oracle handles indexing and null values oddly, suggesting better query performance with explicit values vs. 'where col is [not] null'
I think you are using primitive type as your primary/foreign key columns in your object. If yes then try using wrapper classes. Because primitive types can't have default values as null.

How to add property counted in DB to #Entity class?

I have an Entity. And sometimes I need this object also contains some value, call it 'depth'. The query may look like 'select b.id, b.name, b..., count(c.id) as depth from Entity b, CEntity c where ...'. So I've created class NonHibernateEntity which extends Entity. And then results of query written above are perfectly stored as List of NonHibEntity, which consists of all the fields of Entity (as they are extended), and property 'depth'. I make it by setting aliasToBean results transformer: .setResultTransformer(Transformers.aliasToBean(NHEntity.class)).
But, it is annoying and inconvenient - to specify all the aliases of all the needed fields.
And then, if I want to save one of this object to DB - session.saveOrUpdate((Enity)nHibEntity) - there are an exception about nHibEntity isn't Hibernate Entity.
I heard about storing 'entity' as field in NonHibEntity (aggregation, not inheritance). But it seems this is rather inconvenient too.
What do you think? What is an appropriate solution?
A Formula column mapping may be suitable for your needs. I would try this first.
If this causes performance issues as you fear, you might try a mapped class hierarchy with this field only in the child, and mapping both to the same table. Not sure this will actually work though...
As a last resort, do what you've got now using a non-mapped class, but with the entity as a field in your other class - aggregation instead of inheritance as you say, and make sure there's a way of retrieving the mapped entity from the unmapped one so that you can save. It be sensible to make it a Decorator, so that it's both a subclass and aggregate and you can continue to ignore the distinction in much of your code.
With the non-mapped subclass and/or aggregate, however, you'll have to pull out the entity in order to save.
If somebody want to know - I solved this problem in such way:
just made this calculated field as #Transient, and then
List<BaseEntry> result = new ArrayList<BaseEntry>();
Iterator it = session()
.createQuery("select b, (select count(p.id) as depth from BaseEntry p " +
" where ... ) as d" +
" from BaseEntry b " +
" where ... ")
.list().iterator();
while ( it.hasNext() ) {
Object[] row = (Object[]) it.next();
BaseEntry entry = (BaseEntry) row[0];
Long d = (Long) row[1];
entry.setD(d);
result.add(entry);
}
return result;
It works good, and seems that it can be easily supported in future

Categories