Override Hibernate Annotations - java

I am developing a Java Application that uses Hibernate and is connected to an Oracle instance. Another client is looking to use the same application, but requires it run on MS SQL Server. I would like to avoid making changes to the existing annotations and instead create a package of xml files that we can drop in depending on the environment.
One way to do this is using JPA XML configuration to override the existing class annotations. However, JPA does not support generic generators, which is a requirement due to the structure of our legacy database. The other way that I am looking into is to use Hibernate XML configs to remap entire classes and have access to the generator xml tag. This solution has some issues though:
Hibernate does not allow you to selectively override entity members
Hibernate does not allow you to re-map the same class (e.g. org.hibernate.AnnotationException: Use of the same entity name twice)
Does anyone have any experience with overriding annotations using Hibernate XML Configuration files or is JPA the only way to go?
Update with an Example
In Oracle, Sequences are used to generate unique IDs when inserting new records into the database. An id would then be annotated in the following manner:
#Id
#GeneratedValue(generator="EXAMPLE_ID_GEN", strategy=GenerationType.SEQUENCE)
#SequenceGenerator(name="EXAMPLE_ID_GEN", sequenceName="SEQ_EXAMPLE_ID")
#Column(name = "EXAMPLE_ID")
public String getExampleId() {
return this.exampleId;
}
However, MS SQL Server does not have the concept of Sequences (Ideological differences). Therefore, you could use a table generator to simulate sequences.
#Id
#GeneratedValue(generator="EXAMPLE_ID_GEN", strategy=GenerationType.TABLE)
#TableGenerator(name="EXAMPLE_ID_GEN", tableName="SEQUENCE", valueColumnName="VALUE", pkColumnName="SEQUENCE", pkColumnValue="EXAMPLE_ID")
public String getExampleId() {
return this.exampleId;
}
Two different configurations for two different types of databases. Keep in mind that this is a legacy database and that we aren't going to rewrite our application to support SQL Server identities, the native id generator for SQL Server (which would also require a different annotation).
To alleviate this, I have looked into using Hibernate's #GenericGenerator and point it to a class of my own creation that models org.hibernate.id.SequenceGenerator (or something similar) and also customize the structure of the table by extending org.hibernate.id.TableStructure.
Back to my original question - is any of this possible with an XML override?
How I Solved this Problem
So, in the end, I found that JPA and Hibernate did not provide the out-of-box functionality that I was looking for. Instead, I created a custom generator that checked the database dialect and set the TableStructure appropriately. As I explored all options, I ended up using Hibernate's #GenericGenerator annotation. This is an example of the Id generation annotation:
#Id
#GeneratedValue(generator="EXAMPLE_ID_GEN")
#GenericGenerator(name = "EXAMPLE_ID_GEN", strategy="com.my.package.CustomIdGenerator", parameters = {
#Parameter(name = "parameter_name", value="parameter_value")
})
public String getExampleId() {
return this.exampleId;
}
This solution necessitates that each Hibernate entity be modified with the new Id generator.

I think that if you don't use AnnotationConfiguration when configuring your SessionFactory, the annotations will be omitted.
So, use Configuration.

For your generator problem (for which the solution would normally be "use the native generator" but doesn't work for you due to working with a legacy db), you could probably extend the SQLServerDialect and override the getNativeIdentifierGeneratorClass to return a (possibly custom) generator that does what you need for your legacy db.

I have come across the need to mix-n-match legacy with new schemas/databases before in a Grails (GORM) application, which of course is running Hibernate 3 underneath.
Would not say "you're doing it wrong" - but I would keep the JPA #Annotations to the very basics like #Entity and #Column and leave it to the Hibernate dialect, which is also specified in the XML configuration file.
You might experiment with subclassing the Oracle10gDialect with one that assigns a sequence generator to all tables, versus a Sybase one which does not.
Please see this post on how to implement this.
UPDATE:
What james and I are suggesting (almost in the same minute) is to setup multiple persistence-unit sections of your persistence.xml file.
This allows one to use #Entity and #Id without supplying details in the class. The details come in the hibernate.dialect property. I suggested subclassing Oracle10gDialect (and james the SQLServerDialect) - those would do the choosing as to the table naming, id generator strategy, etc.
See --> https://forum.hibernate.org/viewtopic.php?f=1&t=993012

If you rewrite the annotations in HBM XML files, you could maintain two sets of such XML and pick which ones to use via Hibernate's mapping directives. I've done this in Hibernate Core, but not in a J2EE/JPA environment so I don't know if there are any gotchas in that respect.
The biggest downside is it likely will be a lot of work to remove all your annotations and rebuild them in XML.

I would say that if your annotations are database specific, you're doing it wrong.

In my case:
Rack and Slot are entities having custom ID Generators. I am using unidirectional one-to-one mapping. Dimension table will hold the data with a Autogenerated Custom ID as foreign key for multiple tables (Rack and Slot for example here).
And my schema looks like this : Rack ------> Dimension <-----------Slot
where Dimension will hold the data for Rack and Slot table with Generated ID.
Here the concern is that when i am saving the data like this:-
Rack rack = new Rack(params);
Dimension dim = new Dimension(params);
rack.setDimension(dim);
session.save(rack);
Data is being saved successfully with same Autogenerated ID in Rack and Dimension Tables.
But when I am saving the data for Slot table :
Slot Slot = new Slot(params);
Dimension dim = new Dimension(params);
slot.setDimension(dim);
session.save(slot);
it is showing error message as:-
attempted to assign id from null one-to-one property: rack
Can I pass the dynamic property name as "slot" when saving the data for Slot and Dimension and "rack" when saving the data for Rack and Dimension.
#GenericGenerator(name = "foreign", strategy = "foreign", parameters = {
#Parameter(name = "property", value = "slot"),
#Parameter(name = "property", value = "rack")})
Rack.java
#Entity
#Table(name="tablename")
#GenericGenerator(name = "customseq", strategy = "CustomIdGenerator")
public class Rack {
#Id
#GeneratedValue(generator = "customseq")
#Column(name = "uni_id")
private String id;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private Dimension dimension;
// Getters and Setters
}
Slot.java
#Entity
#Table(name="tablename")
#GenericGenerator(name = "customseq", strategy = "CustomIdGenerator")
public class Rack {
#Id
#GeneratedValue(generator = "customseq")
#Column(name = "uni_id")
private String id;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private Dimension dimension;
// Getters and Setters
}
Dimension.java
public class Dimension implements Serializable{
#Id
#Column(name = "systemid")
#GeneratedValue(generator = "foreign")
#GenericGenerator(name = "foreign", strategy = "foreign", parameters = {
#Parameter(name = "property", value = "slot"),
#Parameter(name = "property", value = "rack")})
private String systemid;
#OneToOne(mappedBy = "dimension", fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn
private Rack rack;
#OneToOne(mappedBy = "dimension", fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn
private Slot slot;
// Getters and Setters
}

Related

How does an existing object retrieve it's changes once we update it somewhere else in repository? [duplicate]

I have a JpaRepository persisting newly created entity in Spring MVC app. This entity looks like this (very simplified):
#Entity
public class Translation {
.....
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToOne(fetch = FetchType.LAZY)
private Version version;
....
}
and Version entity:
#Entity
public class Version {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long id;
#Column(name = "name")
private String name;
#Column(name = "version_code")
private long code;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "version", cascade = {CascadeType.ALL}, orphanRemoval = true)
private Set<Translation> translations;
}
I create a translation object like this
TranslationDTO t = new TranslationDTO();
t.setText(translationText);
ClientVersionDTO version = new ClientVersionDTO();
version.setId(11);
t.setVersion(version);
where 11 is a version that exists in the database already from the very beginning. Please notice that I do not set values for name and code of ClientVersionDTO.
Then I have a service that persists new object (I use dozer library to convert DTO to entities)
#Service
#Transactional
public class TranslationsServiceImpl implements TranslationsService {
#Override
public Long create(TranslationDTO translationDTO) {
Translation translation = translationsConverter.unconvert(translationDTO);
Translation t = translationRepository.saveAndFlush(translation);
Translation t2 = translationRepository.findOne(t.getId());
// !!!! t2.getVersion() returns version where no values are set to 'code' and 'name'
return t2.getId();
}
}
Please notice my comment "t2.getVersion() returns version where no values are set to 'code' and 'name'" - I was expecting so that when I fetch the data from the database, I would get a Version object right from the database with code and name values set. However they are not set. So basically what I get as a t2.getVersion() object is the same object as in input argument translationDTO.getVersion(). How can they I re-invalidate the Version object?
UPDATE tried moving #Transactional to JpaRepository, but still the same result.
If you are using Hibernate, this is the expected result. When you call translationRepository.saveAndFlush(translation) and translationRepository.findOne(t.getId()) one after the other, they hit the same Hibernate session which maintains a cache of all objects that it has worked on. Therefore, the second call simply returns the object passed to the first. There is nothing in those two lines that would have forced Hibernate to fire a SELECT query on the database for the Version entity.
Now, the JPA spec does have a refresh method on the EntityManager interface. Unfortunately, Spring Data JPA does not expose this method using its JpaRepository interface. If this method was available, you could have done t = translationRepository.saveAndFlush(translation) and then versionRepository.refresh(t.getVersion()) to force the JPA provider to synchronize the version entity with the database.
Implementing this method is not difficult. Just extend SimpleJpaRepository class from Spring Data JPA and implement the method yourself. For details see adding custom behaviour to all Spring Data JPA repositories.
An alternate would be to load the version entity as versionRepository.findOne(version.getId()) before setting it on the translation. Since you can hard-code version id in your code, your versions seem to be static. You can therefore mark your Version entity as #Immutable and #Cacheable (the former is a Hibernate-specific annotation). That way, versionRepository.findOne(version.getId()) should not hit the database every time it is called.

Why we use #ForeignKey(name="FK_COUNTRY") annotation?

I have been going through some relational stuff in hibernate where I get this solution for relation between tables I tried this it works fine but when I remove #ForeignKey(name="FK_COUNTRY") nothing change then why are we using this annotation is that came under best practice?
#Entity
#Table(name = "state")
public class State {
#Id
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
#ManyToOne
#ForeignKey(name="FK_COUNTRY")
private Country country;
}
Hibernate should reflect DB structure,
You should read about the importance of foreign key
Referential Integrity
Easier Detective Work
Better performance
If you read the javadoc of #ForeignKey, you'll find:
Used to specify the handling of foreign key constraints when schema generation is in effect. If this annotation is not specified, the persistence provider's default foreign key strategy will be used.
If you don't generate the database schema from the class definitions (e.g. CREATE TABLE SQL statement), then the annotation has no effect.

Embedded field does not work after upgrading to Hibernate 5.0.6.Final in Spring Boot 1.3.1.RELEASE

Having a embeddable class MultiLanguageText:
#Embeddable
public class MultiLanguageText {
#Field
private String textDe;
#Field
private String textFr;
#Field
private String textIt;
//...
}
And another class that uses this class twice:
#Entity(name = "T_AnotherClass")
public class AnotherClass{
#Id
#GeneratedValue
private long key;
#Embedded
private MultiLanguageText name;
#Embedded
private MultiLanguageText description;
//...
}
The fields got translated perfectly fine into "name_textDe", "description_textDe", "name_textFr" and so on with the spring version 1.2.7.RELEASE.
However to store LocalDate's I wanted to upgrade to Hibernate 5. I followed the process described here: https://github.com/spring-projects/spring-boot/issues/2763#issuecomment-154419889
The process worked fine but the translation of the embedded fields stoped working *. I tried different implicit_naming_strategy and physical_naming_strategy, but non of them worked.
If I annotate the fields as follows it does work, but the process is somewhat cumbersome:
#Embedded
#AttributeOverrides({
#AttributeOverride(name = "textDe", column = #Column(name = "name_textDe", length = MultiLanguageText.MAX_TEXT_LENGTH)),
#AttributeOverride(name = "textFr", column = #Column(name = "name_textFr", length = MultiLanguageText.MAX_TEXT_LENGTH)),
#AttributeOverride(name = "textIt", column = #Column(name = "name_textIt", length = MultiLanguageText.MAX_TEXT_LENGTH)),
})
private MultiLanguageText name;
*Not working does mean I get an exception something along the lines of (as now the field gets mapped without the fieldname prefix and therefore the field exist twice):
Caused by: org.hibernate.MappingException: Repeated column in mapping for entity: AnotherClass column: textDe (should be mapped with insert="false" update="false")
at org.hibernate.mapping.PersistentClass.checkColumnDuplication(PersistentClass.java:764)
at org.hibernate.mapping.PersistentClass.checkPropertyColumnDuplication(PersistentClass.java:782)
at org.hibernate.mapping.PersistentClass.checkPropertyColumnDuplication(PersistentClass.java:778)
at org.hibernate.mapping.PersistentClass.checkColumnDuplication(PersistentClass.java:804)
at org.hibernate.mapping.PersistentClass.validate(PersistentClass.java:539)
at org.hibernate.mapping.RootClass.validate(RootClass.java:265)
at org.hibernate.boot.internal.MetadataImpl.validate(MetadataImpl.java:329)
at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:443)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:879)
... 48 more
To fix this in Spring Boot 1.4, add this to your application.yaml:
spring.jpa.hibernate.naming.implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl
From the javadocs:
An ImplicitNamingStrategy implementation which uses full composite
paths extracted from AttributePath, as opposed to just the terminal
property part. Mainly a port of the older
DefaultComponentSafeNamingStrategy class implementing the no longer
supported NamingStrategy contract.
Hibernate official documentation says:
If you want to have the same embeddable object type twice in the same
entity, the column name defaulting will not work as several embedded
objects would share the same set of columns. In plain JPA, you need to
override at least one set of columns. Hibernate, however, allows you
to enhance the default naming mechanism through the NamingStrategy
interface. You can write a strategy that prevent name clashing in such
a situation. DefaultComponentSafeNamingStrategy is an example of this.
Consequently, if you wish to have full JPA compliance, stick with your current implementation.

Need Design help with POJO's used for JPA services and REST Client's

I have a set of JPA POJO's that contain annotations required for mapping to my domain. I also want to expose some REST services that will interact with those domain objects.
My current task is to create an android application to access these REST services. I am not able to use the domain object due to the JPA annotations they contain. The Dalvik compiler complains.
So I am looking for a strategy to be able to leverage these domain objects in a way that an Android project can also use those objects and not have to duplicate those POJO's.
Victor's suggestion to externalise the JPA mappings to XML rather than use annotations would surely work, but might be inconvenient if you're getting your JPA objects from tooling that only generates annotations.
I assume that you need on the client side Java classes that match the objects you will serialise in your REST services.
It is possible, but very tedious, to create DTO objects - POJOs exactly matching the JPA objects with suitable constructors from the JPA objects. This seems like an undue amount of effort.
It must be possible to write a source-code processor to strip the annotations from the Java. I don't think a simple regex scripting solution will work, I guess that truly parsing the source is necessary, so I hesitate to guess how much work this would be. However according to this question's answers the basic set of tools is available. I would start with this approach.
I could work out with following strategy.
This strategy works very well when you dont want fetch whole collection , or fetch with some addition criteria,
, you may retrieve it(collection relation) with named query.
use separate DAO for CRUD operation on JOIN table of many to many relation
e.g.
User can have many accounts and account can be shared by many users.
create domain models/ DAO for all the three tables,
use relation mapping for just retrieval and for DDL use individual properties.
#Entity
#Table(name="account" )
public class Account {
#Id (name="accountid")
private Long accountId;
#Column
private String type;
// removed #OneToMany as it causes issue while serializing to xml
#Transient
private Collection accountUsers;
//rest of the properties n geter setter goes here
}
#Entity
#Table(name="user")
public class User {
#Id(name="userid")
private Long userId;
#Column
private String name;
// by making transient jpa / hibernate does not initialize it with proxy.. so it remains null
/* you can populate this property using named query whenever required .*/
#Transient
private Collection userAccounts;
// rest of the properties n getter setter goes here
}
#Entity
#Table(name="AccountUser")
public class AccountUser {
// whatever your strategy to implement primary key here ,
#Id (name="accountuserid")
private Long accountUserId;
/* please note this annotation , made insertable/updatable false , relation defined just for fetching relation
*/
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "accountid", referencedColumnName = "accountid", insertable = false, updatable = false)
private Account account;
// look at insertable / updatable properties its turned off
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "userid", referencedColumnName = "userid", insertable = false, updatable = false)
private User user;
//
#Column ( name = "userid" )
private Long userId;
#Column ( name = "accountid" )
private Long accountId;
#Column ( name="opendate")
private Date opendate;
}
/* use separate dao to save above beans */
// somthing like this
public class AccountDAOImpl extends GenericDAOImpl implements AccountDAO {
}
public class UserDAOImpl extends GenericDAOImpl implements UserDAO {
}
public class AccountUserDAOImpl extends GenericDAOImpl implements AccountUserDAO {
}
I tried to explain if need any clarification kindly revert back. thanks

Does JPA support mapping to sql views?

I'm currently using Eclipselink, but I know now days most JPA implementations have been pretty standardized. Is there a native way to map a JPA entity to a view? I am not looking to insert/update, but the question is really how to handle the #Id annotation. Every entity in the JPA world must have an ID field, but many of the views I have created do not conform to this. Is there native support for this in the JPA or do I need to use hacks to get it to work? I've searched a lot and found very little information about doing this.
While using the #Id annotation with fields of directly supported types is not the only way to specify an entity's identity (see #IdClass with multiple #Id annotations or #EmbeddedId with #Embedded), the JPA specification requires a primary key for each entity.
That said, you don't need entities to use JPA with database views. As mapping to a view is no different from mapping to a table from an SQL perspective, you could still use native queries (createNativeQuery on EntityManager) to retrieve scalar values instead.
I've been looking into this myself, and I've found a hack that I'm not 100% certain works but that looks promising.
In my case, I have a FK column in the view that can effectively function as a PK -- any given instance of that foreign object can only occur once in the view. I defined two objects off of that one field: one is designated the ID and represents the raw value of the field, and the other is designated read-only and represents the object being referred to.
#Id
#Column(name = "foreignid", unique = true, nullable = false)
public Long getForeignId() {
...
#OneToOne
#JoinColumn(name = "foreignid", insertable=false, updatable=false)
public ForeignObject getForeignObject() {
...
Like I said, I'm not 100% sure on this one (and I'll just delete this answer if it turns out not to work), but it got my code past a particular crash point.
Dunno if it applies to your specific situation, though. And there's an excellent chance that after 11 months, you no longer care. :-) What the hell, that "Necromancer" badge doesn't just earn itself....
In my view I have a "unique" id, so I mapped it as the Entity id.
It works very well:
#Entity
#Table(name="table")
#NamedQuery(name="Table.findAll", query="SELECT n FROM Table n")
public class Table implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="column_a")
private int columnA;
JPA - 2.5.4
CREATE MATERIALIZED VIEW IF NOT EXISTS needed_article as select product_id, count(product_id) as count from product_article group by product_id;
CREATE MATERIALIZED VIEW IF NOT EXISTS available_article as select product_id, count(product_id) as count from article a inner join product_article p
on a.id = p.article_id and a.stock >= p.amount_of group by product_id;
CREATE UNIQUE INDEX productId_available_article ON available_article (product_Id);
CREATE UNIQUE INDEX productId_needed_article ON needed_article (product_Id);
Entity.java
#Entity
#Immutable // hibernate import
#Getter
#Setter
public class NeededArticle {
#Id
Integer productId;
Integer count;
}
Repository.java
#Repository
public interface AvailableProductRepository extends CrudRepository<AvailableArticle, Integer> {
#Query("select available.productId from AvailableArticle available, NeededArticle needed where available.productId = needed.productId and available.count = needed.count")
List<Integer> availableProduct();

Categories