avoid duplication with auto increment key in Hibernate - java

I am trying to use Hibernate to auto increment the id, however, I try to avoid duplication.
class Service
{
Long id; // auto increment
String name;
String owner;
Boolean incremental;
// setter and getter
}
What I want to achieve is, whenever the new service object I want to save has the same name and owner(no matter if the data field incremental are the same or not) as any of the existing one in the database, it will be a duplicated entry. In this case, I don't want to add another entry into the Database anymore. How to revise the hbm.xml files to avoid this issue?

You can use annotations to do the same.
On top of your entity class you write the following:
#Table(uniqueConstraints = #UniqueConstraint(columnNames = { "name","owner"}))
#Entity
class Service
{
Long id; // auto increment
String name;
String owner;
// setter and getter
}
This will tell hibernate that the columns name and owner should be unique together.

If you need the id column, you can keep it.
What you need is
* a unique constraint at the database level on both columns.
(if you use hbmtoddl tool, you may need something like that :
<properties name="key" unique="true">
<property name="name" .../>
<property name="owner" .../>
</properties>
)
This way, you can not insert duplicates data.
After that if you don't want your code to break when you try to insert duplicates, you need to
lookup by name and owner (if you do that often, an index might be a good idea)
if you don't find the entry, insert it
you might also want to catch the exception thrown in case of unique constraint violation (yes that may still happen if two threads are inserting data at the same time) and retry a select.

You have a number of options:
define your primary key as a composite-id or natural-id
before saving, use a query to find if there is another row with the same name and owner, and if there is - get it.
Either way you should override hashCode() and equals(..) using name and owner

Related

Struts2 and Hibernate insert operation error [duplicate]

org.hibernate.HibernateException: identifier of an instance
of org.cometd.hibernate.User altered from 12 to 3
in fact, my user table is really must dynamically change its value, my Java app is multithreaded.
Any ideas how to fix it?
Are you changing the primary key value of a User object somewhere? You shouldn't do that. Check that your mapping for the primary key is correct.
What does your mapping XML file or mapping annotations look like?
You must detach your entity from session before modifying its ID fields
In my case, the PK Field in hbm.xml was of type "integer" but in bean code it was long.
In my case getters and setter names were different from Variable name.
private Long stockId;
public Long getStockID() {
return stockId;
}
public void setStockID(Long stockID) {
this.stockId = stockID;
}
where it should be
public Long getStockId() {
return stockId;
}
public void setStockId(Long stockID) {
this.stockId = stockID;
}
In my case, I solved it changing the #Id field type from long to Long.
In my particular case, this was caused by a method in my service implementation that needed the spring #Transactional(readOnly = true) annotation. Once I added that, the issue was resolved. Unusual though, it was just a select statement.
Make sure you aren't trying to use the same User object more than once while changing the ID. In other words, if you were doing something in a batch type operation:
User user = new User(); // Using the same one over and over, won't work
List<Customer> customers = fetchCustomersFromSomeService();
for(Customer customer : customers) {
// User user = new User(); <-- This would work, you get a new one each time
user.setId(customer.getId());
user.setName(customer.getName());
saveUserToDB(user);
}
In my case, a template had a typo so instead of checking for equivalency (==) it was using an assignment equals (=).
So I changed the template logic from:
if (user1.id = user2.id) ...
to
if (user1.id == user2.id) ...
and now everything is fine. So, check your views as well!
It is a problem in your update method. Just instance new User before you save changes and you will be fine. If you use mapping between DTO and Entity class, than do this before mapping.
I had this error also. I had User Object, trying to change his Location, Location was FK in User table. I solved this problem with
#Transactional
public void update(User input) throws Exception {
User userDB = userRepository.findById(input.getUserId()).orElse(null);
userDB.setLocation(new Location());
userMapper.updateEntityFromDto(input, userDB);
User user= userRepository.save(userDB);
}
Also ran into this error message, but the root cause was of a different flavor from those referenced in the other answers here.
Generic answer:
Make sure that once hibernate loads an entity, no code changes the primary key value in that object in any way. When hibernate flushes all changes back to the database, it throws this exception because the primary key changed. If you don't do it explicitly, look for places where this may happen unintentionally, perhaps on related entities that only have LAZY loading configured.
In my case, I am using a mapping framework (MapStruct) to update an entity. In the process, also other referenced entities were being updates as mapping frameworks tend to do that by default. I was later replacing the original entity with new one (in DB terms, changed the value of the foreign key to reference a different row in the related table), the primary key of the previously-referenced entity was already updated, and hibernate attempted to persist this update on flush.
I was facing this issue, too.
The target table is a relation table, wiring two IDs from different tables. I have a UNIQUE constraint on the value combination, replacing the PK.
When updating one of the values of a tuple, this error occured.
This is how the table looks like (MySQL):
CREATE TABLE my_relation_table (
mrt_left_id BIGINT NOT NULL,
mrt_right_id BIGINT NOT NULL,
UNIQUE KEY uix_my_relation_table (mrt_left_id, mrt_right_id),
FOREIGN KEY (mrt_left_id)
REFERENCES left_table(lef_id),
FOREIGN KEY (mrt_right_id)
REFERENCES right_table(rig_id)
);
The Entity class for the RelationWithUnique entity looks basically like this:
#Entity
#IdClass(RelationWithUnique.class)
#Table(name = "my_relation_table")
public class RelationWithUnique implements Serializable {
...
#Id
#ManyToOne
#JoinColumn(name = "mrt_left_id", referencedColumnName = "left_table.lef_id")
private LeftTableEntity leftId;
#Id
#ManyToOne
#JoinColumn(name = "mrt_right_id", referencedColumnName = "right_table.rig_id")
private RightTableEntity rightId;
...
I fixed it by
// usually, we need to detach the object as we are updating the PK
// (rightId being part of the UNIQUE constraint) => PK
// but this would produce a duplicate entry,
// therefore, we simply delete the old tuple and add the new one
final RelationWithUnique newRelation = new RelationWithUnique();
newRelation.setLeftId(oldRelation.getLeftId());
newRelation.setRightId(rightId); // here, the value is updated actually
entityManager.remove(oldRelation);
entityManager.persist(newRelation);
Thanks a lot for the hint of the PK, I just missed it.
Problem can be also in different types of object's PK ("User" in your case) and type you ask hibernate to get session.get(type, id);.
In my case error was identifier of an instance of <skipped> was altered from 16 to 32.
Object's PK type was Integer, hibernate was asked for Long type.
In my case it was because the property was long on object but int in the mapping xml, this exception should be clearer
If you are using Spring MVC or Spring Boot try to avoid:
#ModelAttribute("user") in one controoler, and in other controller
model.addAttribute("user", userRepository.findOne(someId);
This situation can produce such error.
This is an old question, but I'm going to add the fix for my particular issue (Spring Boot, JPA using Hibernate, SQL Server 2014) since it doesn't exactly match the other answers included here:
I had a foreign key, e.g. my_id = '12345', but the value in the referenced column was my_id = '12345 '. It had an extra space at the end which hibernate didn't like. I removed the space, fixed the part of my code that was allowing this extra space, and everything works fine.
Faced the same Issue.
I had an assosciation between 2 beans. In bean A I had defined the variable type as Integer and in bean B I had defined the same variable as Long.
I changed both of them to Integer. This solved my issue.
I solve this by instancing a new instance of depending Object. For an example
instanceA.setInstanceB(new InstanceB());
instanceA.setInstanceB(YOUR NEW VALUE);
In my case I had a primary key in the database that had an accent, but in other table its foreign key didn't have. For some reason, MySQL allowed this.
It looks like you have changed identifier of an instance
of org.cometd.hibernate.User object menaged by JPA entity context.
In this case create the new User entity object with appropriate id. And set it instead of the original User object.
Did you using multiple Transaction managers from the same service class.
Like, if your project has two or more transaction configurations.
If true,
then at first separate them.
I got the issue when i tried fetching an existing DB entity, modified few fields and executed
session.save(entity)
instead of
session.merge(entity)
Since it is existing in the DB, when we should merge() instead of save()
you may be modified primary key of fetched entity and then trying to save with a same transaction to create new record from existing.

check constraint annotation for allowed values in hibernate

I have Consumer table in database where I have check constraint on consumer_type column like below:
ALTER TABLE consumer
ADD CONSTRAINT Check_consumer_consumer_type CHECK (consumer_type IN ('ACCOUNT','ORGANIZATION'))
Now from hibernate side I want to add check constraint annotation which will allow only "ACCOUNT" and "ORGANIZATION" value in consumer type table.
Which hibernate / jpa annotation I should use for this purpose?
Assuming that you have the consumer type represented using String in your entity you could easily replace it with an enum and Java's type safety would ensure that you will not be able to pass anything else to it.
Using EnumType.STRING, the values will be stored as Strings in your database (and mapped back to enum when you load the entity), so no change to your data would be required.
Example
#Entity
public class Consumer {
public static enum ConsumerType {
ACCOUNT, ORGANIZATION
}
#Enumerated(EnumType.STRING)
#Column(name="consumer_type", nullable=false)
private ConsumerType consumerType;
// Other properties, getters/setters, ...
}

Mapping one DB column to two seperate fields using JPA

I'm developing a code generator that have to generate JPA entities from database meta-model files. These model are from home-brewed modeling system which are being used to generate models other than JPA entities.
In these models some fields are mapping back to same database column. But it seems like JPA does not like that very much. When I try to run generated code I get
Exception [EclipseLink-48] (Eclipse Persistence Services - 2.6.0.v20140809-296a69f): org.eclipse.persistence.exceptions.DescriptorException
Exception Description: Multiple writable mappings exist for the field [FACT_INVENT_TRANS_HIST_DM.TRANSACTION_ID]. Only one may be defined as writable, all others must be specified read-only.
Mapping: org.eclipse.persistence.mappings.DirectToFieldMapping[TransactionIdKey-->FACT_INVENT_TRANS_HIST_DM.TRANSACTION_ID]
Descriptor: RelationalDescriptor(InventTransHistFactDM --> [DatabaseTable(FACT_INVENT_TRANS_HIST_DM)])
As I can't change the models only option left is to make one of those fields read-only. And the JPA entities being generated are only used to read data from database it will not used for writing data. Is there a way to mark some fields as read only or tell EclipseLink that these entities are read only so it does not have to worry about the multiple writable mapping.
I tried using EclipseLink's #ReadOnly annotation in all entities but it did not help this issue.
There is no #ReadOnly in JPA.
There are however attributes "insertable"/"updatable" that you can set against a field via #Column to effectively do the same.
The question may be almost 6 years old, but it's still being found today, so I'd like to address another option:
public class Foobar {
#OneToOne
#JoinColumn(name="SELF_COLUMN_FOO", referencedColumnName = "FOREIGN_COLUMN_TO_JOIN")
public Foo foo;
#OneToOne
#JoinColumn(name="SELF_COLUMN_BAR", referencedColumnName = "FOREIGN_COLUMN_TO_JOIN")
public Bar bar;
}
This can be used where SELF_COLUMN is obviously the relevant column in the Foobar table, and FOREIGN_COLUMN_TO_JOIN would be single key in the other table you wish to join.
This will be useful where you want to have two (or more) attributes in a single class, but only one column to join on the foreign DB table. For example: An Employee may have a home phone number, cell number, and a work phone number. All are mapped to different attributes in the class, but on the database there's a single table of phone numbers and id's, and an identifier column, say VARCHAR(1) with 'H' or 'W' or 'C'. The real example would then be...
Tables:
PHONENUMBERS
PHONENUMBER_ID,
ACTUAL_NUMBER
EMPLOYEE
ID
HOMENUMBER VARCHAR(12),
CELLNUMBER VARCHAR(12),
WORKNUMBER VARCHAR(12)
public class Employee {
#OneToOne
#JoinColumn(name="HOMENUMBER", referencedColumnName = "PHONENUMBER_ID")
public Phone homeNum;
#OneToOne
#JoinColumn(name="CELLNUMBER", referencedColumnName = "PHONENUMBER_ID")
public Phone cellNum;
#OneToOne
#JoinColumn(name="WORKNUMBER", referencedColumnName = "PHONENUMBER_ID")
public Phone workNum;
}
As you can see, this would require multiple columns on the Entity's table, but allows you to reference a foreign key multiple times without throwing the 'Multiple writable mappings exist...' that you showed above. Not a perfect solve, but helpful for those encountering the same problem.

Default naming policy for columns - is it possible? (JPA)

JPA newbie here. Here's my question:
Say we have an entity like this:
#Entity
#Table(name="thingies")
public class Thingy implements Serializable {
private Long thingyId;
private String thingyName;
private Integer thingyPrice;
// Constructor, getters, setters
}
Mapped to a table like this:
create table thingies (
thingy_id serial primary key,
thingy_name text,
thingy_price smallint
);
Is there a way to make the provider aware of the attribute naming policy instead of needing to explicitly provide a #Column annotation on each getter? In other words, can we automatically map all underscored names to the corresponding camelcased names without using #Column?
(I know I can quote names, that is not an answer to my question above.)
You need to add the following property to your persistence.xml file:
<property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy"/>
And all camelcasedproperty names will bemapped to underscored column names.
You should use #Column.
#Column(name="thingy_name")
private String thingyName;
Becouse 'thingy_name' is not equal 'thingyName'. JPA dont know what you want mapped. But in table thingyname and in entity thingyName is equal for JPA.
Use adnotation #Column, it does not take a lot of time. But you will get a explicit documentation.

Manually update the counter used for generation of primary key in Hibernate?

In my spring project, the tables in database are created automatically by Hibernate using my entity classes as base, but I insert some default values in the table manually (using pgAdmin3).
Because that, I am facing now this problem: when I try insert a value via Java code in one of the tables which already have values, I receive a error message, saying the primary key already exists in the database.
Anyone knows how to solve this problem?
UPDATE
That's how I declare my primary key in my class:
#Id
#Column(name = "id")
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
Call this SQL query once per table to set the sequence to the next free number:
SELECT setval('tblname_id_seq', max(id)) FROM tblname;
tblname being the actual name of the table.
Hibernate may use a different naming convention, or the sequence may have been renamed. If you can't find the sequence behind the serial column, check with (per documentation):
SELECT pg_get_serial_sequence(tblname, column_name)
More details:
Modify Django AutoField start value
How to import a CSV to postgresql that already has ID's assigned?
The problem here might be that you declare the id as a primitive instead of a wrapper.
So instead of:
private int id;
You should have:
private Integer id;
When you create the entity with the id is initialized as 0, instead of NULL.
That's why you get duplicate id constraint violation exceptions.
Only when the id is NULL the AUTO generation strategy will delegate the id assignment to the database.

Categories

Resources