Alternative to save in JPA/Hibernate with non autogenerated ID - java

I'm trying to insert a value into a database with type String in ID.
#Entity
#Table(name = "xpto_version_map")
public class XptoVersionMap implements Serializable {
#Id
#Column(name = "uniq_name", unique = true, nullable = false)
private String uniq_name;
...
When trying to save a new XptoVersionMap() like:
XptoVersionMap xptoVersionMap = new XptoVersionMap();
xptoVersionMap.setUniqName("XPTO-1");
xptoVersionMap.setValue("value2");
xptoVersionMapRepository.save(xptoVersionMap);
Will throw:
org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find xxx.api.database.entity.XptoVersionMap with id XPTO-1; nested exception is javax.persistence.EntityNotFoundException: Unable to find xxx.api.database.entity.XptoVersionMap with id XPTO-1.
I've tried different solutions, but unless I can make a native query to insert the value, I can't have a way to tell Hibernate that I want to just check if the #Id (uniq_name) exists, if not insert the new value and not to throw an exception.

That's how Hibernate works by default. It assigns an automated generated ID when you do save method and ID is not set. If there is an ID it tries to update it (what happens in your case).
save Persists an entity. Will assign an identifier if one doesn't
exist. If one does, it's essentially doing an update. Returns the
generated ID of the entity.
You can do a workaround for example by using persist and #PrePersist
#PrePersist
void generateId() {
if (uniq_name == null) {
uniq_name = GENERATE_SOME_UNIQUE_ID_SO_IT_DOESN'T_BREAK();
}
}
And then use xptoVersionMapRepository.persist(xptoVersionMap);
Or you can write your own generator with something like that:
#GenericGenerator(name = "my_generator", strategy = "package.CustomGenerator")
#GeneratedValue(generator = "my_generator")
And then create a class CustomGenerator that implements IdentifierGenerator and create the required methods.

Related

Can I use only Entity's id instead of fetching an entity from DB when there is a relationship in an Entity?

I am using Spring Data JPA with Hibernate.
Lets say I have the following entity defined:
#Entity
#Table(name = "foods")
public class Food {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "food_id")
private Long foodId;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "food_type_id")
#NotNull
private FoodType foodType;
...
}
#Entity
#Table(name = "food_types")
public class FoodType {
public static final Integer PERISHABLE;
public static final Integer NON_PERISHABLE;
#Id
#Column(name = "food_type")
private Integer foodTypeId;
private String name;
...
}
Every time when I want to create a Food entity and save it to the database, currently code looks like this:
Food food = new Food();
FoodType foodType = foodTypeRepository.findById(FoodType.PERISHABLE); // Call to DB to get Entity
food.setFoodType(foodType);
....
foodRepository.save(food);
If we consider FoodType to be constant in the DB. Can I use it like this:
Food food = new Food();
FoodType foodType = new FoodType();
foodType.setFoodTypeId(FoodType.PERISHABLE); // No Call to DB
food.setFoodType(foodType);
....
foodRepository.save(food);
I have tested it and yes I can use it that way, hibernate will save the Food entity, but are there any downsides, pitfalls, etc... I am not seeing.
PS. This is just a simple example illustrating the idea, it is part of old legacy project which I cannot modify to remove constant from DB, and use an enum instead.
To avoid extra call to DB you should use:
FoodType foodType = foodTypeRepository.getOne(FoodType.PERISHABLE);
under the hood it calls EntityManager.getReference that obtain a reference to an entity without having to load its data as opposed to the foodTypeRepository.findById that lead to call EntityManager.find that obtain an entity along with its data.
See also this section of the hibernate documentation.
P.S. You can not use:
Food food = new Food();
FoodType foodType = new FoodType();
foodType.setFoodTypeId(FoodType.PERISHABLE);
as in this case hibernate consider foodType as a transient entity (not associated with a persistence context) and will try to save it as a new record if you have a proper cascading on your #ManyToOne association.
P.S.S. As it's mentioned in the documentation the method JpaRepository#getOne(ID) is deprecated and you should use JpaRepository#getById(ID) instead.
You do not need to fetch the entity associated with FoodType.PERISHABLE in order to set the relation on a Food entity to it and I'm not aware of any side effects or pitfalls of using FoodType.PERISHABLE directly as long it is a valid FoodType id.
As others mentioned, you could also use JpaRepository#getById(ID id) and that's probably the more canonical way of addressing this problem:
T getById(ID id) Returns a reference to the entity with the given
identifier. Depending on how the JPA persistence provider is
implemented this is very likely to always return an instance and throw
an EntityNotFoundException on first access. Some of them will reject
invalid identifiers immediately.

Caused by: org.hibernate.TransientObjectException: The given object has a null identifier: com.models.User on hibernate update

I am trying to make an update in the database row. I am having this exception
Caused by: org.hibernate.TransientObjectException: The given object has a null identifier: com.models.User
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.getUpdateId(DefaultSaveOrUpdateEventListener.java:270)
this is my controller code for the submit action from the jsp file
// create new user object
User user = new User();
user.setName(name);
user.setEmail(email);
user.setActive(false);
_userDao.update(user);
this is my dao that defines the update with hibernate session factory utility
public void update(User user) {
getSession().update(user);
}
//EDITTED: this is my mapping for user entity class
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "userId")
private Integer id;
#Column(nullable = false)
private String name;
#Column(unique = false, nullable = false)
private String email;
#Column(nullable = true)
private boolean active;
I am not able to update the user record where the email address is equal to the one entered in the jsp input form. Kindly assist, this is my first attempt in updating a field with hibernate sessionfactory.
The problem is that you are using update method on not existing entity. If you want to save newly created entity you have to use save or saveOrUpdate.
update method works only if entity already exists in DB.
I had the slimier situation at my work. As #ByeBye said the problem was trying to update an entity which was not persisted before. See following example
SalesOrder salesOrder = consignment.getSalesOrder();
if (salesOrder != null) {
// some code
}
else{
//
salesOrder = new SalesOrder();
consignment.setSalesOrder(salesOrder);
}
salesOrderRepository.update(salesOrder); // hibernate update
return salesOrder;
}
Here when the execution of code comes to else part. It try to create a new sales order object (where the object id is null ) and try to update it. And this cause the mentioned error.
So my fix was simply changing the update to saveOrUpdate . see below
.......
salesOrder = new SalesOrder();
consignment.setSalesOrder(salesOrder);
}
salesOrderRepository.saveOrUpdate(salesOrder);
return salesOrder;
}
So then it commands hibernate to first persist the non-existing objects and then do all the updates upon that (if required ).
Hope this scenario will help to all

How to use ORMLite foreign objects WITHOUT generated id?

I am learning how to use ORMLite with android. My problem is that I receive objects with an ID from the server and I think it would be good to use the same ID for my DB. This means I am not using generatedId = true and therefore cannot use foreignAutoGenerate = true.
public class Artwork {
#DatabaseField(id = true, columnName = "id")
String id;
#DatabaseField
String name;
#DatabaseField
String file;
#DatabaseField(columnName = "user_id", foreign = true, foreignAutoCreate = true)
User owner;
}
As you can see, Artwork references the user who owns it. Both already have IDs on the server side that I would like to use as IDs for my DB.
public class User {
#DatabaseField(id = true, unique = true)
String id;
#DatabaseField
String name;
}
And below is where the magic should happen...
Artwork artwork = new Artwork();
artwork.setName("name");
artwork.setFile("filepath");
artwork.setId("generated_by_server_0000");
User owner = new User();
owner.setId("generated_by_server_0001")
owner.setName("user");
artwork.setOwner(owner);
DatabaseHelper dbHelper = OpenHelperManager.getHelper(this, DatabaseHelper.class);
Dao<Artwork, String> artworkDao = dbHelper.getArtworkDao();
Dao<User, String> userDao = dbHelper.getUserDao();
userDao.create(owner);
artworkDao.create(artwork);
List<Artwork> artworksOnDb = artworkDao.queryForAll();
How can I easily persist those objects using ORMLite but setting the ID myself?
My problem is that I receive objects with an ID from the server and I think it would be good to use the same ID for my DB. This means I am not using generatedId = true and therefore cannot use foreignAutoGenerate = true
Right. You don't not have to do generatedId = true with a foreign object but unfortunately you do need to do it with foreignAutoCreate = true because otherwise ORMLite wouldn't know if the User needs to be created or not. If you are using your own id, you'll need to use the UserDao and create the User directly and not rely on the auto mechanism.
To quote the docs for foreignAutoGenerate:
Set this to be true (default false) to have the foreign field automatically created using an internal DAO if its ID field is not set (null or 0). So when you call dao.create() on the parent object, any foreign field that has this set to true will possibly generate an additional create call via an internal DAO. By default you have to create the object using its DAO directly. By default you have to create the object using its DAO directly. This only works if generatedId is also set to true.
One thing that it is important to realize is that you have to insert the User before you insert the Artwork because the Artwork stores a user_id in its table.
User owner = new User();
owner.setId("generated_by_server_0001")
owner.setName("user");
...
// do this _before_ the create of Artwork
userDao.create(owner);
Artwork artwork = new Artwork();
artwork.setName("name");
...
artwork.setOwner(owner);
artworkDao.create(artwork);

One-to-one mapping hibernate and not null

I'm new to hibernate and quite new to MySQL too.
I have the following two tables:
CREATE TABLE storeman.user (
id INT NOT NULL AUTO_INCREMENT,
email VARCHAR(80) NOT NULL,
display_name VARCHAR(50),
password CHAR(41),
active BOOLEAN NOT NULL DEFAULT FALSE,
provisional BOOLEAN NOT NULL DEFAULT FALSE,
last_login TIMESTAMP,
PRIMARY KEY (id),
UNIQUE INDEX (email)
);
CREATE TABLE storeman.user_preferences (
id INT NOT NULL AUTO_INCREMENT,
notify_login BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (id),
CONSTRAINT id_foreign FOREIGN KEY (id) REFERENCES user (id) ON DELETE CASCADE
);
In Eclipse, with hibernate tools I have generated the domain code classes. User.java looks like this (siplified):
#Entity
#Table(name = "user", catalog = "storeman", uniqueConstraints = #UniqueConstraint(columnNames = "email"))
public class User implements java.io.Serializable {
private Integer id;
[...]
private UserPreferences userPreferences;
public User() {
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
public Integer getId() {
return this.id;
}
[...]
#OneToOne(fetch = FetchType.LAZY, mappedBy = "user")
public UserPreferences getUserPreferences() {
return this.userPreferences;
}
}
My issue is with getUserPreferences: of course, that would return null if creating a new user or reading from the db where the corresponding row in the user_detail table does not exist. This is correct, however it forces me to check if userPreferences is not null before accessing its members. And from a coding point of view it is not so handy. So I changed User.getUserPreferences method like this, to get a default value:
#OneToOne(fetch = FetchType.LAZY, mappedBy = "user")
public UserPreferences getUserPreferences() {
if (this.userPreferences==null)
this.userPreferences = new UserPreferences();
return this.userPreferences;
}
This is working fine, however if I ever would need to re-generate domain code (User.java) with hibernate tools, that change will be lost. So my question is: is there a way (even by modifying mySQL table/relationships) to automatically have userPreferences always set?
There is no way to do this outside of your code (at least not that I can think of), with some configuration or something like that.
One thing you can do is to initialize the relation when you declare it
private UserPreferences userPreferences = new UserPreferences()
but that also won't survive code regeneration. The only other way I can think of is to put this initialization code into some util method so you can maintain it there regardless of regeneration of entity code.
UserUtils.getUserPreferences(User user)
However, this would only work for the code you write, if some framework needs it you will again get null values because it will not use your util method (the first approach is better in this case).
Do bear in mind that, when you initialize this object on a managed entity, the new object will be persisted into the database.
User user = userDAO.getUser(id);
user.getUserPreferences(); // this code initializes the relation (new UserPreference())
After these lines, you will get a row in user_preferences table if cascade is configured in that manner, or you will get an exception complaining about transient entity found in entity you are trying to persist.
All that being said, is it really that hard to check if something is null, especially if by business rules it is allowed to be null?

Hibernate error: ids for this class must be manually assigned before calling save():

Caused by: org.springframework.orm.hibernate3.HibernateSystemException: ids for this class must be manually assigned before calling save(): com.rfid.model.Role; nested exception is org.hibernate.id.IdentifierGenerationException: ids for this class must be manually assigned before calling save(): com.rfid.model.Role
at org.springframework.orm.hibernate3.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:676)
at org.springframework.orm.hibernate3.HibernateAccessor.convertHibernateAccessException(HibernateAccessor.java:412)
at org.springframework.orm.hibernate3.HibernateTemplate.doExecute(HibernateTemplate.java:424)
at org.springframework.orm.hibernate3.HibernateTemplate.executeWithNativeSession(HibernateTemplate.java:374)
at org.springframework.orm.hibernate3.HibernateTemplate.saveOrUpdate(HibernateTemplate.java:748)
at com.wfos.engine.wrapper.domain.impl.WrapperImpl.save(WrapperImpl.java:159)
... 47 more
Caused by: org.hibernate.id.IdentifierGenerationException: ids for this class must be manually assigned before calling save(): com.rfid.model.Role
at org.hibernate.id.Assigned.generate(Assigned.java:53)
at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:121)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:210)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:195)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:117)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93)
at org.hibernate.impl.SessionImpl.fireSaveOrUpdate(SessionImpl.java:685)
at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:677)
at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:673)
at org.springframework.orm.hibernate3.HibernateTemplate$16.doInHibernate(HibernateTemplate.java:751)
at org.springframework.orm.hibernate3.HibernateTemplate.doExecute(HibernateTemplate.java:419)
... 50 more
WARN [21:14:21] (CommonsLoggingOutput.java:59): - --Erroring: batchId[1] message[java.lang.reflect.UndeclaredThrowableException]
My class is like this:
#Entity
#javax.persistence.Table(name="Role")
#Table(appliesTo = "Role")
public class Role {
#Id
#Column(name="U_id")
public String U_id;
public String U_pwd;
public String U_account;
public String U_mode;
public String U_status;
public String getU_pwd() {
return U_pwd;
}
public void setU_pwd(String u_pwd) {
U_pwd = u_pwd;
}
public String getU_account() {
return U_account;
}
public void setU_account(String u_account) {
U_account = u_account;
}
public String getU_id() {
return U_id;
}
public void setU_id(String u_id) {
U_id = u_id;
}
public String getU_mode() {
return U_mode;
}
public void setU_mode(String u_mode) {
U_mode = u_mode;
}
public String getU_status() {
return U_status;
}
public void setU_status(String u_status) {
U_status = u_status;
}
}
Your #Entity class has a String type for its #Id field, so it can't generate ids for you.
If you change it to an auto increment in the DB and a Long in java, and add the #GeneratedValue annotation:
#Id
#Column(name="U_id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long U_id;
it will handle incrementing id generation for you.
Resolved this problem using a Sequence ID defined in Oracle database.
ORACLE_DB_SEQ_ID is defined as a sequence for the table. Also look at the console to see the Hibernate SQL that is used to verify.
#Id
#Column(name = "MY_ID", unique = true, nullable = false)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator = "id_Sequence")
#SequenceGenerator(name = "id_Sequence", sequenceName = "ORACLE_DB_SEQ_ID")
Long myId;
For hibernate it is important to know that your object WILL have an id, when you want to persist/save it. Thus, make sure that
private String U_id;
will have a value, by the time you are going to persist your object. You can do that with the #GeneratedValue annotation or by assigning a value manually.
In the case you need or want to assign your id's manually (and that's what the above error is actually about), I would prefer passing the values for the fields to your constructor, at least for U_id, e.g.
public Role (String U_id) { ... }
This ensures that your object has an id, by the time you have instantiated it. I don't know what your use case is and how your application behaves in concurrency, however, in some cases this is not recommended. You need to ensure that your id is unique.
Further note: Hibernate will still require a default constructor, as stated in the hibernate documentation. In order to prevent you (and maybe other programmers if you're designing an api) of instantiations of Role using the default constructor, just declare it as private.
Here is what I did to solve just by 2 ways:
make ID column as int type
if you are using autogenerate in ID dont assing value in the setter of ID. If your mapping the some then sometimes autogenetated ID is not concedered. (I dont know why)
try using #GeneratedValue(strategy=GenerationType.SEQUENCE) if possible
I got same error for different scenario. In my case my primary key of table is a VARCHAR value, so in the entity class(Entity1) ID is a String value, can't set it to auto generate. Also this entity join with another entity(Entity2).
When I set value of Entity1 to empty in updating Entity2 and calling save(), it's throw this exception. There setting empty string for id not work.
Entity2 e2 = new Entity2();
Entity1 e1 = new Entity1();
e1.setId("");
e2.setEntity1(e1);
session.update(e2);
Also setting null for id not work.
Entity2 e2 = new Entity2();
Entity1 e1 = new Entity1();
e1.setId(null);
e2.setEntity1(e1);
session.update(e2);
This way it's work.
Entity2 e2 = new Entity2();
e2.setEntity1(null);
session.update(e2);
Got this error with H2 database when mismatched type for numerical id value. Generated and populated table via number of sql statements and set id to be INT. Later created Entity Java class and set id to be Long. Issue was resolved after adjusting type of id in Java class.
if you want to give id's value manually like JSON or other way you can add #JsonProperty(" U_id") notation before your U_id variable
FROM UI you have to pass the ID (primary Key) Then observer you are saving or updating
if you are saving use save method or use saveOrUpdate method
change class field value in your entity.hbm.xml to identity or sequense like
<hibernate-mapping>
<class name="models.Manager" table="Managers">
<id name="id">
<generator class="identity"></generator>
</id>
<property name="name"></property>
<property name="surname"></property>
<property name="department"></property>
<property name="project_id"></property>
</class>
and add if you still dont have these code for your entity id:
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
that worked for me
I got this problem because I had 2 Id's annotations inside my entity class, one above my id field and one above my getter method
so make sure that there is only one Id annotation above your id field

Categories