Spring with Hibernate: Duplicate entry for key 'PRIMARY' - java

I have developed a REST- API with Spring Web and Hibernate.
I deployed it in two server instances and it runs without any problem for about 5 months. Now it is mostly working but in some periods "MySQLIntegrityConstraintViolationException" with message "Duplicate entry '235648' for key 'PRIMARY'" are thrown (te duplicate id is changing in the exceptions).
The class, for which the exception is thrown, looks like this:
#Entity
#Table(name = "Metadata", catalog = "data")
public class Metadata{
private Long id;
private String field1;
private String field2;
//...
#Id
#Column(unique = true, nullable = false)
#GeneratedValue(strategy = GenerationType.TABLE)
public Long getId() {
return id;
}
//More Getters and Setters...
}
There was neither change to java code nor changes on the MySql-database.
Do you have any idea why it stopped working properly?

Most likely, the number of requests which end up creating entities which use that table for id generation has increased.
One of the remedies would be to catch that exception in a parent method and retry (probably this would be some kind of PessimisticLock exception as the id table has to be physically locked while retrieving and updating its content).
Another one would be to increase the allocationSize option, which for you is 50 being the default if no custom set-up is done. You would need to reenter the hibernate default table / columns names as this has been already created in the database:
#Id
#Column(unique = true, nullable = false)
#TableGenerator(
name="tableGen",
table="hibernate_sequences",
pkColumnName="sequence_name",
valueColumnName="next_val",
pkColumnValue="default",
allocationSize=100
#GeneratedValue(strategy = GenerationType.TABLE, generator="tableGen")
public Long getId() {
return id;
}
Here is a nice post which explains the scalability pitfalls of a TABLE generation strategy -> link

#GeneratedValue(strategy = GenerationType.IDENTITY)
worked for me.

Related

idMoneda property not updated in DB with #Transient directive

I have this entity, in which I have made a PUT and POST method, which do not give an error but nevertheless the idMoneda, which is a property calculated with #Transient because it is the ID of the moneda(where there is a 1 to 1 relationship with another table), it does not update me, when I look at the database it remains null even though in the POST request I put a value. I don't know if it's because the setter is wrong, or just that something else needs to be added that I don't see right now.
#Entity
#Table(name = "REMESA")
public class Remesa {
#Id
#SequenceGenerator(name = "remesa_sequence", sequenceName = "remesa_sequence", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "remesa_sequence")
#Column(name = "ID")
private Long id;
#OneToOne
#JoinColumn(name = "moneda", nullable = true)
#JsonIgnore
private Moneda moneda;
#Transient
#JsonProperty("moneda")
private Long idMoneda;
public Long getIdMoneda() {
return this.moneda.getId();
}
public void setIdMoneda(Long idMoneda) {
this.idMoneda = idMoneda;
}
}
#Transient in JPA means: do not save this field in DB. A column named "moneda_id" will automatically be generated by your relationship if it's well-defined
Java's transient keyword is used to denote that a field is not to be serialized, whereas JPA's #Transient annotation is used to indicate that a field is not to be persisted in the database, i.e. their semantics are different. Because they have different meanings.
So try to remove the transient annotation and run your code .

How to provide Initial value OR Increment ID with JPA GenerationType.AUTO

I am using following code to define MyEntity,
#Entity
#Table(name = "MY_TABLE")
public class MyEntity {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "MY_TABLE_ID")
private Integer myTableId;
#Column(name = "MY_TABLE_NM")
private String myTableName;
//Getters Setters
}
For the first POST after my application starts, I create MyEntity everything works fine, MY_TABLE_ID starts with 1 and works as expected.
My issue is, If somebody inserts data manually before I do my POST then I get duplicate key exception as myTableId is entered as 1 which is already present.
My main problem is I can't create database sequence for using GenerationType.SEQUENCE now to resolve this as database can't be altered now.
I have tried various combinations of GenerationType, TableGenerator but I am unable to successfully tackle it.
Setting initialValue to some larger number to avoid duplicate values can temporarily resolve my problem but I am unable to do it too.
If someone can help me with initialValue with AUTO or give me some other better solution without database changes will be great :)
As MY_TABLE_ID is an identity column, following annotations will work.
#Entity
#Table(name = "MY_TABLE")
public class MyEntity {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY) // <-- IDENTITY instead of AUTO
#Column(name = "MY_TABLE_ID")
private Integer myTableId;
#Column(name = "MY_TABLE_NM")
private String myTableName;
//Getters Setters
}
The identity column will automatically assign an value as soon as the transaction is committed. You are not to set any values for an identity column, as its the job of the database to assign the values. Therefore you also don't need to think about any initial values (forget them completely for identity columns)
I tried various options in answers provided here and for similar questions on stackoverflow and other forums,
I had few limitations,
I couldn't create database sequence as my database changes were freezed.
I didn't want to introduce new Custom IdGenerator class because it would add confusion to other people working with me.
It was resolved using following change:
Adding GenericGenerator with increment strategy helped me, I made following changes to my code.
#Entity
#Table(name = "MY_TABLE")
public class MyEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator="seq")
#GenericGenerator(name = "seq", strategy="increment")
#Column(name = "MY_TABLE_ID")
private Integer myTableId;
#Column(name = "MY_TABLE_NM")
private String myTableName;
//Getters Setters
}
It helped me because,
From Hiberbate DOCs
increment
An IdentifierGenerator that returns a long, constructed by counting
from the maximum primary key value at startup. Not safe for use in a
cluster!
Since, it was incrementing already existing myTableId even if it was manually inserted, this resolved my issue.
You can also implement your own generator if you need more control.
See this interface IdentifierGenerator.
So you can get the count of records, for example through a #NamedQuery.
Then you can generate an identifier yourself.
public class MyEntityKeyGenerator implements IdentifierGenerator {
#Override
public Serializable generate(SessionImplementor session, Object object) {
// SELECT count(ent) from MyEntity ent;
Long count = (Long) session.getNamedQuery("count-query").uniqueResult();
// calc and return id value
}
}
Entity:
class MyEntity {
#Id
#GenericGenerator(name = "my_generator",
strategy = "org.common.MyEntityKeyGenerator")
#GeneratedValue(generator = "my_generator")
private Long id;...
Just do not forget about the lock.
I use the generation type Identity, which basically means that the db, takes care of Id generation.
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#MappedSuperclass
#EntityListeners(EntityListener.class)
#EqualsAndHashCode(of = {"id", "createdAt"})
public abstract class AbstractEntity<ID extends Serializable> implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private ID id;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "CREATED_AT", updatable = false)
private Date createdAt;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "UPDATED_AT")
private Date updatedAt;
}
You can also use, Sequence generation:
#Entity
#SequenceGenerator(name="seq", initialValue=1, allocationSize=100)
public class EntityWithSequenceId {
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="seq")
#Id long id;
}

How to automatically generate id from 1 with JPA?

I am working on rest web services. I found some issue with autogenerated Id with JPA and Spring Boot.
Here are models:
#Entity
public class Post {
#Id #GeneratedValue
private Long id;
private String postText;
#ManyToOne
private BlogUser user;
private LocalDateTime createdDate;
}
#Entity
public class Comment {
#Id #GeneratedValue
private Long id;
private String commentText;
Saving objects looks like following:
Post firstPost = Post.builder()
.postText("First post !!! UUUUUhuuuuu!")
.user(carlos)
.createdDate(LocalDateTime.now())
.build();
Post secondPost = Post.builder()
.postText("I like this blog posting so much :)")
.user(carlos)
.createdDate(LocalDateTime.now())
.build();
Post thirdPost = Post.builder()
.postText("To be or not to be? What is the question.")
.user(carlos)
.createdDate(LocalDateTime.now())
.build();
postService.addPost(firstPost);
postService.addPost(secondPost);
postService.addPost(thirdPost);
BlogUser sailor = BlogUser.builder()
.userName("sailor").password("123").email("sailor#gmail.com").build();
userService.addUser(sailor);
Comment commentToFirstPost = Comment.builder().commentText("you an idiot!")
.user(sailor).post(firstPost).createdDate(LocalDateTime.now()).build();
Comment secondCommentToFirstPost = Comment.builder().commentText("You should sail to Antarctica!")
.user(sailor).post(firstPost).createdDate(LocalDateTime.now()).build();
However, after it I have instances in DB:
Posts:
1 First post
2 Second post
3 Third post
Comments:
4 First comment
5 Second comment
I want to make comments iteration from 1 because it is completely another class. Not related to posts. It should be like the following:
1 First comment
2 Second comment
UPDATE:
DB is PostgreSQL. Also, I am interested to know how to do it for MySQL.
How to solve this issue?
When you use the vanilla #GeneratedValue, its set-up with a javax.persistence.GenerationType.AUTO, which:
Indicates that the persistence provider should pick an appropriate
strategy for the particular database.
In most cases that would be actually the GenerationType.SEQUENCE.
In that case hibernate would use its internal sequence for field annotated with the plain / vanilla style like yours.
That would explain that the counter does not restart for each of the entities as the same sequence is used there.
You could try forcing the native id generation though:
#GeneratedValue(strategy = GenerationType.IDENTITY)
Do something like this:
public class Post
{
#Id
#SequenceGenerator(name="seq",sequenceName="my_seq")
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="seq")
private Integer id;
}
Use a different sequence for each entity.
Use initialValue attribute of TableGenerator
#Id
#TableGenerator(name = "COMMENT_GEN",
table = "id_gen",
pkColumnName = "seq_name",
valueColumnName = "seq_number",
initialValue = 1)
#GeneratedValue(strategy = GenerationType.TABLE, generator = "COMMENT_GEN")
private Long id;

Hibernate sequence generator always 0

I'm trying to configure a mapped class to use a sequence I defined in a postgres db. The ids are always zero when I try to persist any entities, if I use select nextval('item_seq'), I'll get 1 (or the next val). I used intellij to regenerate the classes. The version of hibernate in this project is 3.6.0, if that might be part of my problem?
#Entity
#Table(name = "item")
public class Item {
private int itemid;
...
#Basic
#Id
#SequenceGenerator(name = "item_seq", sequenceName = "item_seq", allocationSize = 1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator = "item_seq")
#Column(name = "itemid", unique = true, nullable = false, insertable = true, updatable = true)
public int getItemid() {
return itemid;
}
...
}
Usage
Item item = new Item();
item.setCreated(new Date());
item.setVendorId(vendorId);
save(item); // essentially is getHibernateTemplate().save(Item.class.getName(), item);
-- EDIT --
I've been trying the suggestions below and everything seems to keep generating 0 as an id or throw the exception 'ids for this class must be manually assigned before calling save()'. This is where I am now, as a last ditch effort I tried moving the annotations to the variable declaration instead of the getter. Didn't help.
#Entity
#Table(name = "item")
public class Item {
#Id
//#SequenceGenerator(name = "item_seq", sequenceName = "item_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.IDENTITY) //, generator = "item_seq")
#Column(name = "itemid", unique = true, nullable = false) //, insertable = true, updatable = true)
private Long itemid;
...
public Long getItemid() {
return itemid;
}
}
This always works for me (Hibernate 4.x though):
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Id makes the column a primary key (unique, no nulls), so you don't need the #Column(...) annotation. Is something setting your itemId somewhere? You can remove its setter if you have one (Hibernate doesn't require).
Try to provide database schema name for given table and sequence in you entity class.
Make sure your application user has privileges (grants) to the sequence.
-- update --
I think solution is here
I was having the same problem and here's what I did to solve the issue
I was using Hibernate tools to auto generate POJOs and all the annotations were being placed at the method level, however, Spring recommends (requires?) them at the field level. You can't just move the Id annotations to the field level either, because it's either one or the other (I got some other exceptions when I tried this). So I followed this answer to customize Hibernate tools to generate POJOs with the annotations all at the field level.
Now my code looks like this and it's using the database sequence sequence just fine.
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="my_seq")
#SequenceGenerator(name="my_seq",sequenceName="SPT_PROJECT_SEQ", allocationSize=1)
#Column(name = "PROJECT_ID", unique = true, nullable = false, precision = 10, scale = 0)
private long projectId;
Hope this helps
what database u are using?
can be possibly a database that does not support sequence??
try to use strategy=auto and see how does it work
since 'select nextval(...)' works, your database (postgresql) is supporting sequence. so thats not it
maybe for some reason hibernate is threating yout int == 0 as an id and it is trying to update it instead of inserting a new record ( deafult value for int =0 ) just change your id type to Integer and see if it solve the problem

Persisting #OneToMany relations with #JoinColumn and #MapKeyColumn

I have two entities:
#Entity
Article {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(fetch = FetchType.EAGER, cascade=CascadeType.ALL)
#JoinColumn(name="embed_id", referencedColumnName="id")
#MapKeyColumn(name = "language")
#MapKeyEnumerated(EnumType.ORDINAL)
private Map<Language, Locale> locales;
Article() {
locales.put(Language.CS, new Locale());
locales.put(Language.EN, new Locale());
}
}
#Entity
Locale {
#Id
private Long embed_id;
#Id
private Language language;
#Column(length = 256)
private String name;
}
Thanks to the constructor, I can make sure, that once an Article is instantiated, two Locales (with CascadeType.ALL) are associated with it.
The problem comes when I try to persist such entity - I am getting:
javax.persistence.EntityExistsException:
a different object with the same identifier value was already associated
with the session: org...Locale#org...Locale#2bfdsc64
The problem is that neither embed_id, nor language have assigned values from the Article when persisting and Hibernate does not associate them after persisting Article. How can this be done?
Edit 1:
I checked that when the embed_id and language are set manually, everything works correctly. I just need to tell Hibernate, to set the value of #JoinColumn and #MapKeyColumn according to the #OneToMany relation.
Edit 2:
The problem with MapKeyColumn has an easy solution:
public void addLocale(Language l, Locale locale) {
locale.setLanguage(l);
this.locales.put(l);
}
But I am still unable to tell hibernate to associate the Locale.embed_id from Article.id.
Edit 3:
I can see that the ID of article is properly generated, but then it is not put in the locale:
DEBUG org.hibernate.event.internal.AbstractSaveEventListener -
Generated identifier: 87, using strategy: org.hibernate.id.SequenceGenerator
DEBUG org.hibernate.event.internal.AbstractSaveEventListener -
Generated identifier: component[language,embedId]{language=0, embedId=null}, using strategy: org.hibernate.id.CompositeNestedGeneratedValueGenerator
DEBUG org.hibernate.event.internal.AbstractSaveEventListener -
Generated identifier: component[language,embedId]{language=1, embedId=null}, using strategy: org.hibernate.id.CompositeNestedGeneratedValueGenerator
I guess the problem is, that you want to persist two empty locales. And because you don't use a generator for the id-fields, the locales have the same (empty) primary key and therefore can't be persisted.
I finally found the anwswer! The trick was to create Setter on Article and add Access to id as follows:
#Id
#Getter
#Access(AccessType.PROPERTY)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
public void setId(Long id) {
this.id = id;
this.getLocale(Language.CS).setEmbedId(id);
this.getLocale(Language.EN).setEmbedId(id);
}

Categories