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);
}
Related
I'v been searching internet for answer, but nothing was working for me. There is a lot of topics with similar cases but specific details are different in a way that make them unusable for me.
So I have two tables: t_item and t_item_info:
item_id field from t_item_info table references id field from t_item table. I'm using mysql db and id column form t_item is auto incremented
I need to make unidirectional one-to-one mapping in a specific way. Here are my classes:
#Entity
#Table(name = "t_item")
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
#PrimaryKeyJoinColumn(name = "id", referencedColumnName = "item_id")
private ItemInfo info;
}
And other one
#Entity
#Table(name = "t_item_info")
public class ItemInfo {
#Id
private Long itemId;
private String descr;
}
So the point is that i need Item object to have a reference to ItemInfo object. NOT The other way!
Item -> ItemInfo --YES
Item <- ItemInfo --NO
The other thing is that i need parent (Item) id to become id for a child (ItemInfo)
For example I create Item object with null id and set it's info field with ItemInfo object which also have null id field. Like this:
{
"id": null,
"name": "Some name",
"info": {
"itemId": null,
"descr": "some descr"
}
}
Then when Item object persists hibernate should generate id for parent(Item) object and set it as itemId field for child(ItemInfo).
I have been trying to achieve this with different hibernate annotations and I noticed that no matter how hard I tried Hibernate always seems to try to persist child object first. I noticed it in the logs when I turned sql logging on. insert into t_item_info always goes first (and dies because of null id :D)
So the question is: Is it even possible to achieve this and if so what should I change in my code to do so
I hope that what I'm trying to ask makes sens to you given my poor explanations =)
Why people always insist the child object table in one-to-one associations should be the one with the foreign key is beyond me.
Anyway, as a workaround, since both objects share the id and the association is non-optional, you might as well declare the autogenerated key for the child object:
#Entity
#Table(name = "t_item_info")
public class ItemInfo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long itemId;
private String descr;
}
and then use #MapsId for the parent:
#Entity
#Table(name = "t_item")
public class Item {
#Id
private Long id;
private String name;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "id")
#MapsId
private ItemInfo info;
}
Note that this approach, will, in a sense, fool Hibernate into thinking it is the Item that should be treated as the child object. You have been warned.
While there is an accepted answer here it looks to me like #Secondary table would be a better and more convenient solution: you may have 2 tables at the database level but I do not see any reason that that fact needs be exposed to any client code. There does not seem to be a lot of benefit to that? The following gives you a simpler API.
Entity:
#Entity
#Table(name = "t_item")
#SecondaryTable("t_item_info", pkJoinColumns={
#PrimaryKeyJoinColumn(name="id", referencedColumnName="item_id")})
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#Colulumn(name = "description", table= "t_item_info"")
private String description;
}
API:
{
"id": null,
"name": "Some name",
"descr": "some descr"
}
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;
}
I have a simple table (ActivityLog) and I want it to have a PK that is also a FK to another table (User).
It seems to be a common thing to have, and I tried to follow this wikibook
Primary Keys through OneToOne and ManyToOne Relationships. The example there involved a composite key. I need just a primitive key, so I ended up with:
#Entity
public class User {
#Id
private Long id;
// other stuff
}
#Entity
public class ActivityLog {
#Id
#OneToOne(optional = false)
#JoinColumn(name="user_id", referencedColumnName="id")
private User user;
// other stuff
}
Unfortunately i am getting:
Caused by: java.lang.IllegalArgumentException: This class [class com.example.ActivityLog] does not define an IdClass
at org.hibernate.metamodel.internal.AbstractIdentifiableType.getIdClassAttributes(AbstractIdentifiableType.java:183)
at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation$IdMetadata.<init>(JpaMetamodelEntityInformation.java:253)
I tried to annotate ActivityLog with:
#IdClass(Long.class)
(even though from what I understand it is applicable only for composite keys), yet I am getting the exact same error.
Is my case different than what's on the mentioned wikibook?
Is Spring at fault here? (As suggested in this question? (no accepted answers)).
This should help:
#Entity
public class ActivityLog {
#Id
#Column(name = "user_id")
private Long id;
#OneToOne(optional = false)
#JoinColumn(name="user_id", referencedColumnName="id")
private User user;
// other stuff
}
Btw. I would expect, that you need more logs per user, so you would probably need some additional (generated) id anyway ...
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.
I have a many-to-one relationship and I like that the last shared reference should get deleted by hibernate automatically. The questions are
is this is supported by hibernate?
if not can I achieve this by adding some kind of api callbacks from JPA/Hibernate rather then fully code it in the DAOs by myself?
Example I have an "Attribute" (Name/Value Pair) which is an entity and its shares some "Translation" for its name with other Attributes. So if an attribute get deleted hibernate should check if still another attribute exists where the same translation is used. If there is no one left the translation should be deleted as well.
#Entity
public class Attribute {
#Id
#GeneratedValue
private int id;
private String name;
#Lob
private String value;
#ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.PERSIST)
#JoinColumn(name="name_translation_id")
private Translation nameTranslation;
...
}
#Entity
public class Translation {
#Id
#GeneratedValue
private int id;
#ElementCollection (fetch=FetchType.LAZY)
#CollectionTable(name= "translation_values", joinColumns = #JoinColumn(name = "translation_id"))
#MapKeyColumn(name="language_code")
#Column(name = "value")
#Lob
private Map<String, String> values = new HashMap<String, String>();
...
}
I am using hibernate v4.3.
I think Jpa Entity Listeners good choice for you
for your question write one metod and anotate #PostRemove in Attribute.class
#PostRemove
public void removeTranslationByAttribute(Attribute attribute){
List<Attribute> attributes = AttributeRepository.getByNameTranslationId(attribute.getNameTranslation()); //get all atribute by `name_translation_id`
if(attributes.size() == 0) // when not include atrribute in list`name_translation_id`
TranslationRepository.deleteById(attribute.getNameTranslation()); // delete translation object by `name_translation_id`
}