Extend entity classes with composite keys in hibernate - java

In our company we have a strange database model which can't be modified because to many systems works with them. Up to know we have a straight java application which connects with hibernate to the database and loads the data. We have for each table one xml mapping file.
The strange thing about the database is that we do not have any primary keys. Most table have a unique index containing several columns.
Now we want to use an application server (jboss) and the ejb model. So I created a class like this:
#Entity
#Table (name = "eakopf_t")
public class Eakopf implements Serializable {
#Embeddable
public static class EakopfId implements Serializable {
private String mandant;
private String fk_eakopf_posnr;
// I removed here the getters and setters to shorten it up
}
#Id
private EakopfId id;
private String login;
// I removed the getters and setters here as well
}
This works perfect.
Because our customers have different versions of the database schema I thought about extending this class on each database release change. So each interface we create with java can decide which version of the table will be used.
Here is the extended table class
#Entity
#Table (name = "eakopf_t")
public class Eakopf6001 extends Eakopf implements Serializable {
private String newField;
// getters and setters
}
If I use Eakopf (the base version) it is working if I do something like that:
EakopfId id = new EakopfId();
id.setMandant("001");
id.setFk_eakopf_posnr("ABC");
Eakopf kopf = (Eakopf) em.find(Eakopf.class, id);
But if I do this:
EakopfId id = new EakopfId();
id.setMandant("001");
id.setFk_eakopf_posnr("ABC");
Eakopf6001 kopf = (Eakopf6001) em.find(Eakopf6001.class, id);
this exception occues
javax.ejb.EJBException: javax.persistence.PersistenceException:
org.hibernate.WrongClassException: Object with id:
de.entity.Eakopf$EakopfId#291bfe83 was not of the specified subclass:
de.entity.Eakopf (Discriminator: null)
Does anybody has an idea?
many greetings,
Hauke

Doing what you did means to Hibernate that you're storing two different kinds of entities in a single table. This is possible is you use a discriminator column. But if I understand correctly, you just want one kind of entity in the table : Eakopf6001. In this case, its base class should be annotated with #MappedSuperClass, not with #Entity.
I would suggest creating a class annotated with #MappedEntity (let's call it BaseEakopf), and two entities: EaKopf and EaKopf6001, each with their set of additional fields. Include one of the other of the entities in the list of mapped classes, depending on which one you want to use.
My personal opinion is that if you have multiple versions of your app, they should use the same entities, but with different fields. Your version control system would take care of these multiple versions, rather than your source code (i.e. have one set of source files per version of the app, rather than one single set of source files for all the possible versions).

Related

Changing package name of hibernate managed entities

After doing some refactoring moving some classes into different packages, I started seeing following error while querying the database with criteria builder:
java.lang.IllegalArgumentException: Parameter value [in.helpi.ironlegion.db.hibernate.entity.UserEntity#1863fc] did not match expected type [in.helpi.ironlegion.cerebro.db.hibernate.entity.UserEntity
If I change the package name back to in.helpi.ironlegion.cerebro.db.hibernate.entity it works just fine.
Update
I am able to properly fetch Individual entities. But when I go for querying entities having reference to other entity I get this error. For example:
public class CommunityAccessEntity extends BaseEnity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#ManyToOne
#JoinColumn(name = "UserEntity_id")
private UserEntity userEntity;
...
}
If I query it on user using criteria builder like:
query.select(root).where(criteriaBuilder.equal(root.get(CommunityAccessEntity_.userEntity), user)));
I get the above error.
Has somebody also faced similar issues..
You must have implemented Serializable interface in your entity classes as it is one of the thumb rules of entity class.
Java serialization is tightly coupled with class name and package name. Your data is stored in database with your old package entity. Now you changed the package name and system will not be able to find the records in DB with your new package. This is why when you restore the package, it works.
If you are using xml based configuration for hbm, please check hbm files whether new package have been updated in all the places.

Safe embedded entity with objectify

I have two entities.
#Entity
public class Recipe {
#Id
private Long id;
private List<Step> steps;
}
#Entity
public class Step {
#Id
private Long id;
private String instruction;
}
And the following Clound Endpoint
#ApiMethod(
name = "insert",
path = "recipe",
httpMethod = ApiMethod.HttpMethod.POST)
public Recipe insert(Recipe recipe) {
ofy().save().entities(recipe.getSteps()).now(); //superfluous?
ofy().save().entity(recipe).now();
logger.info("Created Recipe with ID: " + recipe.getId());
return ofy().load().entity(recipe).now();
}
I'm wondering how do I skip the step where I have to save the emebedded entity first. The Id of neither entity is set. I want objectify to automatically create those. But if don't save the embedded entity I get an exception.
com.googlecode.objectify.SaveException: Error saving com.devmoon.meadule.backend.entities.Recipe#59e4ff19: You cannot create a Key for an object with a null #Id. Object was com.devmoon.meadule.backend.entities.Step#589a3afb
Since my object structure will get a lot more complex, I need to find a way to skip this manual step.
I presume you are trying to create real embedded objects, not separate objects stored in the datastore and linked. Your extra save() is actually saving separate entities. You don't want that.
You have two options:
Don't give your embedded object an id. Don't give it #Entity and don't give it an id field (or at least eliminate #Id). It's just a POJO. 90% of the time, this is what people want with embedded objects.
Allocate the id yourself with the allocator, typically in your (non-default) constructor.
Assuming you want a true embedded entity with a real key, #2 is probably what you should use. Keep in mind that this key is somewhat whimsical since you can't actually load it; only the container object can be looked up in the datastore.
I suggest going one step further and never use automatic id generation for any entities ever. Always use the allocator in the (non-default) constructor of your entities. This ensures that entities always have a valid, stable id. If you always allocate the id before a transaction start, it fixes duplicate entities that can be created when a transaction gets retried. Populating null ids is just a bad idea all around and really should not have been added to GAE.
The concept of the embedded is that the embedded content is persisted inside the main entity.
Is this the behaviour you are trying to configure?
The default behaviour of a Collection (List) of #Entity annoted class is to refer them instead of embed them. As you current configuration, the List<Step> variable does not have any annotation to override the default configuration, which is a different entity related to another one.
The error you are getting is because Objectify, when it saves the recipe entity, is trying to get the key of each step to create the relationship (and save them in the recipe entity), but if the entity step is not saved yet on the datastore, does not have a key
If you are trying to persist the steps inside the recipe entity, you need to setup objectify like this
#Entity
public class Recipe {
#Id
private Long id;
private List<Step> steps;
}
public class Step {
private Long id;
private String instruction;
}
As you can see, I removed the #Id annotation (an embedded Entity does not require an ID because is inside another entity) and the #Entity from the Step class. With this configuration, Objectify save the step entities inside the recipe entity
Source: https://code.google.com/p/objectify-appengine/wiki/Entities#Embedded_Object_Native_Representation

Is it possible to map a normal Java bean for Hibernate in a separate class / file?

I have a normal model class
public class Person {
private int id;
private String name;
...
}
It's a model class and doesn't have any JPA / Hibernate annotations used.
Is it possible to somehow tell Hibernate to make this class persistent?
I want to use Person in queries, criteria etc, but don't want to introduce annotations to that class (it is defined in a model JAR with no JPA dependency, and I have the DB code in a different JAR);
Hibernate supports XML mappings to map a class to a database, as well as annotations.

Persisting third-party classes with no ID's

Say I have the following Java class, which is owned by a vendor so I can't change it:
public class Entry {
private String user;
private String city;
// ...
// About 10 other fields
// ...
// Getters, setters, etc.
}
I would like to persist it to a table, using JPA 2.0 (OpenJPA implementation). I cannot annotate this class (as it is not mine), so I'm using orm.xml to do that.
I'm creating a table containing a column per field, plus another column called ID. Then, I'm creating a sequence for it.
My question is: is it at all possible to tell JPA that the ID that I would like to use for this entity doesn't even exist as a member attribute in the Entry class? How do I go about creating a JPA entity that will allow me to persist instances of this class?
EDIT
I am aware of the strategy of extending the class and adding an ID property it. However, I'm looking for a solution that doesn't involve extending this class, because I need this solution to also be applicable for the case when it's not only one class that I have to persist, but a collection of interlinked classes - none of which has any ID property. In such a scenario, extending doesn't work out.
Eventually, I ended up doing the following:
public class EntryWrapper {
#Id
private long id;
#Embedded
private Entry entry;
}
So, I am indeed wrapping the entity but differently from the way that had been suggested. As the Entry class is vendor-provided, I did all its ORM work in an orm.xml file. When persisting, I persist EntryWrapper.
I don't have much experience with JPA, but I wouldn't extend your base classes, instead I would wrap them:
public class PersistMe<T> {
#Id
private long id;
private T objToWrap;
public(T objToWrap) {
this.objToWrap = objToWrap;
}
}
I can't test it, if it doesn't work let me know so I can delete the answer.

Morphia - change class associated with a collection

I'm trying to phase out an older java codebase that uses MongoDB/Morphia. During this transition, I'd like the new platform to write to the same MongoDB database/collections so that each can live side by side for a little while. That part I'm doing alright with. My issue is that in the new platform, I need a different package/class structure for the objects I'm mapping with morphia than what is currently in the collection.
For instance, in the old platform I've got this class:
package com.foo;
#Entity
public class Bar {
#Id private String id;
private String name;
...
}
In my mongo database, I now have a collection "Bar" and its documents have the className attribute set to "com.foo.Bar". That's all wonderful.
What I'd like to do in the new platform is create a brand new class in a different package to represent that entity, but have it interact with mongo in the same way. I'm hoping to be able to do something like this:
package com.foo.legacy;
#Entity("com.foo.Bar")
public class LegacyBar {
#Id private String id;
private String name;
...
}
I realize the above doesn't work, but if I change the annotation to #Entity("Bar") I don't get any errors, but when I look up entities by id, I always get null back.
So... is there any way for me to have 2 separate VMs with 2 class structures and 2 different configurations of Morpha such that each can write to the same database/collection in the same fashion?
If I change LegacyBar to just "Bar" and create it in a package called "com.foo" then everything works as expected. I would just REALLY prefer to have the flexibility to quarantine all of this legacy data in a semi-clean fashion.
Do you even need the className attribute?
You can disable it with
#Entity(value = "Bar", noClassnameStored = true)
and drop the attribute in the database.
Quoting the official documentation:
Why would you need it?
This is mainly used when storing different
entities in the same collection and reading them back as the base or
super class.
If you don't do this, it should be an easy workaround to allow different package structures.

Categories

Resources