Migrating Hibernate mixed inheritance from hbm.xml to orm.xml - java

Consider the following Java hierarchy:
I want the whole hierarchy to be stored in a single table named file, except for SpecialOutFile which should have its dedicated special_file table.
This works perfectly well using hbm.xml mappings:
<?xml version="1.0" encoding="UTF-8"?>
<hibernate-mapping xmlns="http://www.hibernate.org/xsd/hibernate-mapping"
default-access="field"
default-cascade="all"
default-lazy="true">
<class name="com.example.demo.File">
<id name="id"/>
<discriminator column="file_type"/>
<subclass name="com.example.demo.InFile" discriminator-value="InFile"/>
<subclass name="com.example.demo.TypedOutFile1" discriminator-value="TypedOutFile1"/>
<subclass name="com.example.demo.TypedOutFile2" discriminator-value="TypedOutFile2"/>
<subclass name="com.example.demo.OutFile" discriminator-value="OutFile"/>
</class>
<class name="com.example.demo.SpecialOutFile" table="special_file">
<id name="id"/>
<property name="moreData"/>
</class>
</hibernate-mapping>
When I persist an OutFile, only one record is inserted, in file.
When I persist a SpecialOutFile, only one record is inserted, in special_file.
Now I'd like to do the same in orm.xml, as hbm.xml became deprecated in Hibernate 6. However, I cannot manage to reproduce the exact same behavior.
The closest I can get is using a secondary table, but now when I persist a SpecialOutFile two records are created: one in file and one in special_file:
Hibernate: insert into file (some_data, type, id) values (?, 'SpecialOutFile', ?)
Hibernate: insert into special_file (more_data, id) values (?, ?)
Here's my current version of orm.xml:
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_2_0.xsd">
<access>FIELD</access>
<entity class="com.example.demo.File">
<discriminator-column name="type"/>
<attributes>
<id name="id"/>
</attributes>
</entity>
<entity class="com.example.demo.InFile">
<discriminator-value>InFile</discriminator-value>
</entity>
<entity class="com.example.demo.TypedOutFile1">
<discriminator-value>TypedOutFile1</discriminator-value>
</entity>
<entity class="com.example.demo.TypedOutFile2">
<discriminator-value>TypedOutFile2</discriminator-value>
</entity>
<entity class="com.example.demo.OutFile">
<discriminator-value>OutFile</discriminator-value>
</entity>
<entity class="com.example.demo.SpecialOutFile">
<secondary-table name="special_file"/>
<attribute-override name="id">
<column table="special_file"/>
</attribute-override>
<attributes>
<basic name="moreData">
<column table="special_file"/>
</basic>
</attributes>
</entity>
</entity-mappings>
Is there a way to move all inherited attributes into the secondary table and completely get rid of the primary table when persisting a SpecialOutFile, like I was able to do with hbm.xml mappings?
Minimal reproducible exemple here: https://github.com/bjansen/so-question-75328749

I think what you are looking for is discriminator based joined inheritance: https://en.wikibooks.org/wiki/Java_Persistence/Inheritance#Example_joined_inheritance_XML
You'd just mention the same table name for every class except for the SpecialOutFile one.

Related

Set default fetch strategy to LAZY for entity ( XML)?

I have an entity defined in my XML persistence file as shown below. The entity/table in question has over 100 fields, but I only need a handful. Rather than marking all the fields I don't need with a fetch="LAZY" attribute, can I make the default fetch strategy for all columns for this table be LAZY and only mark the ones I need as fetch="EAGER" ?
I looked thru the Open JPA documentation here, but I did not see any reference to this. Is this possible in the XML entity-mappings?
<entity class="Users">
<table schema="dbo" name="Users"/>
<attributes>
<id name="id">
<column name="Id" column-definition="nchar" length="18"/>
</id>
<basic name="about" fetch="LAZY">
<column name="About" column-definition="nvarchar" length="1000"/>
</basic>
<basic name="accountId">
<column name="AccountId" column-definition="nchar" length="18"/>
</basic>
<basic name="alias">
<column name="Alias" column-definition="nvarchar" nullable="false" length="8"/>
</basic>
<basic name="auditCounter">
<column name="AuditCounter"/>
</basic>
<basic name="auditPercent">
<column name="AuditPercentc"/>
</basic>
<basic name="auditTarget">
<column name="Audit"/>
</basic>
<basic name="CenterId">
<column name="CenterId" column-definition="nchar" length="18"/>
</basic>
</attributes>
</entity>
It seems an entity graph is what you're after.
https://docs.oracle.com/javaee/7/tutorial/persistence-entitygraphs001.htm
What the above link implies is that you could mark the fields you need to be fetched eagerly, then use an empty load graph with your queries.
Or, you could use a fetch graph, explicitly listing the properties you're interested in, to control the fetch mode on a per-query basis.
The confusing part about the entity graph specification is that basic fields need to be explicitly marked as eagerly fetched as well, despite that (as far as annotation-based configuration goes, at least), the default value for Basic.fetch property is already FetchMode.EAGER. I have no idea what the behavior will be with XML-based configuration, so I suggest you go with the fetch graph approach and make sure it actually works.

How to reload entity mapping file in dynamic-map mode of Hibernate without restarting the application?

Hibernate ORM has a feature called Dynamic Models, which enables you to define entity with XML file, instead of POJO. Say I have a table called EVENTS defined as following.
CREATE TABLE events
(
event_id bigint NOT NULL,
event_date timestamp without time zone,
title character varying(255),
CONSTRAINT events_pkey PRIMARY KEY (event_id)
)
And the mapping file defined as following:
<class entity-name="Event" table="EVENTS">
<id name="id" column="EVENT_ID" type="long">
<generator class="increment"/>
</id>
<property name="date" type="timestamp" column="EVENT_DATE"/>
<property name="title" type="string"/>
</class>
Now I added a column to table EVENTS by alter table events add column col_1 varchar(30), and inserted an record by INSERT INTO events (event_id, event_date, title, col_1) VALUES (4, current_date, 'test title3', 'col1 test');
Also I added one line into the mapping file as following
<class entity-name="Event" table="EVENTS">
<id name="id" column="EVENT_ID" type="long">
<generator class="increment"/>
</id>
<property name="date" type="timestamp" column="EVENT_DATE"/>
<property name="title" type="string"/>
<property name="col_1" type="string"/>
</class>
However I can't retrieve the newly added column if I don't restart the application to make Hibernate reload the mapping file. Is it possible to reload the entity schema from mapping file without restarting the application? Thanks.

Order By <collection element's property> in many-to-many association

I have three tables. Order, Location, Order_Location where Order_Location is table that holds many-to-many relation.
Order has List<Location>. Location has property called city. Using HQL (Hibernate 3.6 for Java), I want to get all the locations for a particular order, ordered by the city.
In hbm file, List<Location> is mapped using idbag. Though I got the HQL, the generated SQL query is having join to Location and Order_Location table twice which I feel is overhead.
What am I doing wrong here?
SELECT o.locationList FROM Order o
join o.locationList locList
where o.orderId = 1
order by locList.city desc
which translates to something like below
select
order4_.LOC_ID as order1_355_,
order4_.LOC_CODE as order2_355_,
order4_.CITY as order3_355_,
order4_.CITY_LONG_NAME as order4_355_
from
sche.order order0_
inner join
sche.order_location order1_
on order0_.ORDER_ID=order1_.ORDER_ID
inner join
sche.location order2_
on order1_.LOC_ID=order2_.LOC_ID
inner join
sche.order_location order3_
on order0_.ORDER_ID=order3_.ORDER_ID
inner join
sche.location order4_
on order3_.LOC_ID=order4_.LOC_ID
where
order0_.ORDER_ID=1
order by
order2_.city desc
========= EDIT
Order.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"
>
<hibernate-mapping>
<class name="collectionorderby.Order" table="ORDER">
<id name="orderId" type="string">
<column name="ORDER_ID" length="32" />
<generator class="uuid" />
</id>
<idbag name="locationList" lazy="false" table="ORDER_LOCATION" fetch="select">
<collection-id column="ORDER_LOCATION_ID" type="string">
<generator class="uuid" />
</collection-id>
<key>
<column name="ORDER_ID" length="32" not-null="true" />
</key>
<many-to-many column="LOC_ID" class="collectionorderby.Location"
fetch="join" />
</idbag>
</class>
</hibernate-mapping>
Location.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
<hibernate-mapping>
<class name="collectionorderby.Location" table="LOCATION">
<id name="locId" type="string">
<column name="LOC_ID" length="50" />
</id>
<property name="locCode" type="string">
<column name="LOC_CODE" length="50" />
</property>
<property name="city" type="string">
<column name="CITY" length="50" />
</property>
<property name="cityLongName" type="string">
<column name="CITY_LONG_NAME" length="500" />
</property>
</class>
</hibernate-mapping>
==== EDIT
Noticed that when we provide order by, translated query gets the select from first instance of table where as the order by is done using the second instance of the table. This will not happen, I suppose, if we avoid the duplicate instance of these tables.
The following worked as expected. The alias locList was used in select instead of o.locationList
SELECT locList FROM Order o join o.locationList locList where o.orderId = 1 order by locList.city desc

HibernateException: Foreign key may not be null

I have the following pretty simple many-to-one relationship between Person and their parents/children (which also are instances of Person again).
Person.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 04.05.2011 15:02:31 by Hibernate Tools 3.3.0.GA -->
<hibernate-mapping>
<class name="test.Person" table="PERSONS">
<id name="id" type="long" access="field">
<column name="PERSON_ID" />
<generator class="native" />
</id>
<bag name="children" table="PERSONS" lazy="false" inverse="true" cascade="all">
<key column="PERSON_ID" not-null="false"></key>
<one-to-many class="test.Person" />
</bag>
<many-to-one name="parent" column="PARENT_ID" not-null="false" />
</class>
</hibernate-mapping>
Now, the problem in my case is that I have instances of Person which do not have parents (e.g. orphans).
If I try to persist these objects, I get:
java.sql.SQLException: null, message
from server: "Column
'PARENT_ID' cannot be null"
If I set not-null="true" in the mapping file, I get:
org.hibernate.PropertyValueException:
not-null property references a null or
transient value: test.parent
What's the magic trick here?
Valmar, since you are getting a SQL Exception, probably your COLUMN 'PARENT_ID' have a not null constraint. Check your table DDL. What database are you using?

Hibernate Query Problem

I am using Hibernate. My database is as follows
A Category has many attributes
class category
contains
private Set <Attribute> AllAttributes= new HashSet <Attribute>();
class attribute
How do I retrieve all categories together with their attributes because I am trying 'from category' but it is not working
Category Mapping File:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated Dec 16, 2010 8:37:02 AM by Hibernate Tools 3.4.0.Beta1 -->
<hibernate-mapping>
<class name="com.BiddingSystem.Models.Category" table="CATEGORY">
<id name="CategoryId" type="long">
<column name="CATEGORYID" />
<generator class="native" />
</id>
<property name="CategoryName" type="java.lang.String">
<column name="CATEGORYNAME" />
</property>
<many-to-one name="ParentCategory" class="com.BiddingSystem.Models.Category">
<column name="PARENT_CATEGORY_ID" />
</many-to-one>
<set name="SubCategory" lazy="false" cascade="all-delete-orphan" inverse="true">
<key>
<column name="PARENT_CATEGORY_ID" />
</key>
<one-to-many class="com.BiddingSystem.Models.Category" />
</set>
<set name="AllAttributes" table="ATTRIBUTE" inverse="false" lazy="true" cascade="all">
<key>
<column name="CATEGORYID" />
</key>
<one-to-many class="com.BiddingSystem.Models.Attribute" />
</set>
</class>
</hibernate-mapping>
Attribute Mapping File:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated Dec 16, 2010 5:25:09 AM by Hibernate Tools 3.4.0.Beta1 -->
<hibernate-mapping>
<class name="com.BiddingSystem.Models.Attribute" table="ATTRIBUTE">
<id name="AttributeId" type="long">
<column name="ATTRIBUTEID" />
<generator class="native" />
</id>
<property name="AttributeName" type="java.lang.String">
<column name="ATTRIBUTENAME" />
</property>
<set name="Options" table="ATTRIBUTEOPTION" inverse="false" cascade="all">
<key>
<column name="ATTRIBUTEID" />
</key>
<one-to-many class="com.BiddingSystem.Models.AttributeOption" />
</set>
</class>
</hibernate-mapping>
You have mapped the association with lazy="true". This tells hibernate that by default, a category's attributes should only be loaded from the database when they are actually accessed, in contrast to lazy="false" which would instruct hibernate to load the attributes whenever it loads a category. However, the directive in the mapping file affects all queries.
In case you want it only for a particular query, check out
http://docs.jboss.org/hibernate/core/3.5/reference/en/html/queryhql.html#queryhql-joins :
A "fetch" join allows associations or
collections of values to be
initialized along with their parent
objects using a single select. This is
particularly useful in the case of a
collection. It effectively overrides
the outer join and lazy declarations
of the mapping file for associations
and collections. See Section 20.1,
“Fetching strategies” for more
information.
from Cat as cat
inner join fetch cat.mate
left join fetch cat.kittens
A fetch join does not usually need to
assign an alias, because the
associated objects should not be used
in the where clause (or any other
clause). The associated objects are
also not returned directly in the
query results. Instead, they may be
accessed via the parent object. The
only reason you might need an alias is
if you are recursively join fetching a
further collection:
from Cat as cat
inner join fetch cat.mate
left join fetch cat.kittens child
left join fetch child.kittens
The fetch construct cannot be used in
queries called using iterate() (though
scroll() can be used). Fetch should be
used together with setMaxResults() or
setFirstResult(), as these operations
are based on the result rows which
usually contain duplicates for eager
collection fetching, hence, the number
of rows is not what you would expect.
Fetch should also not be used together
with impromptu with condition. It is
possible to create a cartesian product
by join fetching more than one
collection in a query, so take care in
this case. Join fetching multiple
collection roles can produce
unexpected results for bag mappings,
so user discretion is advised when
formulating queries in this case.
Finally, note that full join fetch and
right join fetch are not meaningful.

Categories

Resources