java hibernate envers set other primary key for audit table - java

I want to map a Map in JPA Hibernate. The set up looks like
#Entity(name = "reservation")
#Table(name = "reservation")
#Access(AccessType.FIELD)
#Audited
public class ReservationEntity {
// other fields
#ElementCollection(fetch = FetchType.EAGER)
#MapKeyColumn(name = "discountType")
#Column(name = "discountAmount")
#CollectionTable(
name="discountTypeAndAmount",
joinColumns=#JoinColumn(name="reservation_id")
)
private Map<DiscountType, BigDecimal> discountTypeAndAmount;
}
I can write the entity to the database the first time, but when I update the entity, I get the following error upon entitymanager.getTransaction().commit():
Caused by: javax.persistence.EntityExistsException: A different object with
the same identifier value was already associated with the session :
[discountTypeAndAmount_AUD#{REV=DefaultRevisionEntity(id = 3,
revisionDate = Dec 20, 2016 8:52:45 PM), element=10.00,
ReservationEntity_reservation_id=1, mapkey=CASE_STUDY}]
In the exception, CASE_STUDY is one of the enums. discountTypeAndAmount_AUD is the audit log table auto generated.
It looks like the audit table discountTypeAndAmount was generated with a composite key made up of REV (revision id), reservation_id, discountType, and discountAmount and the error is thrown because envers doesn't know how to handle BigDecimal as part of a primary key.
Is there an annotation to set the primary key for the audit table to be a composite of just REV (revision id), reservation_id, and discountType? Since the field is a map anyway, there is really no need to have discountAmount as part of the primary key.

#MapKeyColumn is used to specify mapping for the key column if key is a basic type, however I am not sure, but I guess that DiscountType is entity itself, so you need to map your key using #MapKeyJoinColumn.

Related

JPA mapping for SQL Server UNIQUEIDENTIFIER with `default NEWID()`

I am struggling to insert JPA mapped entities (via a Spring Data CrudRepository) into a table on SQL Server 2019 that has a primary key column of type UNIQUEIDENTIFIER and a default value of NEWID().
CREATE TABLE some_table
(
id UNIQUEIDENTIFIER not null primary key default NEWID(),
-- ...
)
The problem is that all variations I managed to get to work involve a UUID generator on the JPA/Hibernate side and I end up with two different UUID values in the table and the saved JPA entity object.
E.g. this mapping "works" in the sense that there is no error reported, but the saved entity object's id field contains a different UUID than what is saved as a PK value in the underlying table.
#Entity
#Table(name = "some_table")
public class MyEntity {
#Id
#Column(columnDefinition = "uniqueidentifier")
#GeneratedValue
private UUID id;
// ...
}
// id field not set
myEntityRepository.save(myEntity);
// if field set, but value differs from DB
Using #GeneratedValue (strategy = GenerationType.IDENTITY) yields a
org.hibernate.id.IdentifierGenerationException: unrecognized id type : uuid-binary -> java.util.UUID
despite the columndefinition.
How do I have to set up the mapping to either correctly pick up the UUID value generated by SQL Server or store the UUID value generated by a Hibernate generator strategy?

Used only #mappedBy , not #JoinColumn - join column created in OneToMany relationship, what is then the use of JoinColumn?

I am new to Hibernate. I have a OneToMany relationship with bidirectional mapping between Account and Transaction. I am not using #JoinColumn in either class and using #mappedBy in the non owning Account class. And everything is working fine. Using H2 in memory database, new join column is created in Transaction table. Then what is the use of #JoinColumn in OneToMany relationships? Is it for unidirectional mapping only? Below is the code. I also read for reference JPA JoinColumn vs mappedBy
public class Account {
#OneToMany( mappedBy="account", cascade=CascadeType.ALL)
List<Transaction> list= new ArrayList<Transaction>();
}
public class Transaction {
#ManyToOne
Account account;
}
Application class :
Account a = new Account("savings");
Transaction t1 = new Transaction("shoe purchase", 45);
t1.setAccount(a);
a.getList().add(t1);
accountRepository.save(a);
output:
Transaction table has an entry with foreign key which is account number in that one row in Account table. ACCOUNT_ID column in created in Transaction table.
There are no extra tables created.
Jpa works on the idea of configuration by convention. So, it will perform configuration on your behalf whenever it can. Think of the #Column annotation, you don't have to apply it on every entity attribute, you would need it only when you have to change something about the attributes.
It's the same with #JoinColumn, when you added #ManyToOne, Jpa already knows that you will need the join column and thus was added for you and the default naming convention for the foreign key was applied (attributename_primarykeyoftheothertype).
Use of
mappedBy
is instruct framework to enable bi-directional relationship. Because of #ManyToOne on Transaction class you Your Transaction Table will have foreign key referring to Account table primary key. By default, Hibernate generates the name of the foreign key column based on the name of the relationship mapping attribute and the name of the primary key attribute. In this example, Hibernate would use a column with the name account_id to store the foreign key to the Account entity.
#JoinColum
can be used If you would like override default foreign key name like #JoinColum(name="acc_id")
MappedBy intructs Hibernate that the key used for the association is on the other side of the association.Like in this case ACCOUNT_ID is being created on Account table.
That means that although you associate two tables together, only one table is having foreign key constraint to the other one.
MappedBylets you to still associate from the table not having foreign key constraint to the other table.

JPA provider uses the wrong database type for collection table

My JPA entity has a map, where key - the other entity, value - boolean type.
It's defined as:
#ElementCollection
#CollectionTable(name = "join_table_name",
joinColumns = {#JoinColumn(name ="owner_entity_key_name") })
#MapKeyColumn(name = "other_entity_key_name")
#Column(name = "name_for_boolean")
private Map<OtherEntity, Boolean> map;
Both the owner entity (where map is defined) and the other entity (that is used as a key in the map) have #Id Integer id; field.
In integration tests the jpa provider uses the following create table query:
create table join_table_name (
owner_entity_key_name integer not null,
name_for_boolean boolean,
other_entity_key_name binary(255) not null,
primary key (owner_entity_key_name, name_for_boolean))
As you see the type of the other_entity_key_name is binary(255), but not integer as I expect.
Any ideas why it works in such manner?
JPA Provider:
Hibernate Core {4.1.6.Final}
PS:
I've used columnDefinition = "integer" to set type explicitly, but the issue is still open cause it's not the best option, I hope.
PPS: now I'm getting an exception when the connection between entities is flushed:
Caused by: org.h2.jdbc.JdbcSQLException: Data conversion error
converting
"X'aced00057372003a636f6d2e6272616e646d616b65722e6d6d732e646f6d61696e2e636f6d6d6f6e2e5669727475616c4461746162617365456e746974794265616e00000000000000010200064c000269647400134c6a6176612f6c616e672f496e74656765723b4c00046e616d657400124c6a6176612f6c616e672f537472696e673b4c000d6e65656473417070726f76616c7400134c6a6176612f6c616e672f426f6f6c65616e3b4c00137365744469676974616c57617465726d61726b71007e00034c001273657456697375616c57617465726d61726b71007e00034c00197669727475616c4461746162617365546f564442474c6973747400104c6a6176612f7574696c2f4c6973743b7870737200116a6176612e6c616e672e496e746567657212e2a0a4f781873802000149000576616c7565787200106a6176612e6c616e672e4e756d62657286ac951d0b94e08b0200007870000000647400165649525455414c5f44425f4e414d452069643d313030737200116a6176612e6c616e672e426f6f6c65616ecd207280d59cfaee0200015a000576616c75657870007371007e000a0171007e000b7372002f6f72672e68696265726e6174652e636f6c6c656374696f6e2e696e7465726e616c2e50657273697374656e74426167464a645c192e1ec40200014c000362616771007e00047872003e6f72672e68696265726e6174652e636f6c6c656374696f6e2e696e7465726e616c2e416273747261637450657273697374656e74436f6c6c656374696f6e87cca6e4d54cc52d02000949000a63616368656453697a655a000564697274795a000b696e697469616c697a65645a000d737065636a4c617a794c6f61644c00036b65797400164c6a6176612f696f2f53657269616c697a61626c653b4c00056f776e65727400124c6a6176612f6c616e672f4f626a6563743b4c0004726f6c6571007e00024c001273657373696f6e466163746f72795575696471007e00024c000e73746f726564536e617073686f7471007e000f7870ffffffff00000071007e000871007e0005740054636f6d2e6272616e646d616b65722e6d6d732e646f6d61696e2e636f6d6d6f6e2e5669727475616c4461746162617365456e746974794265616e2e7669727475616c4461746162617365546f564442474c697374707070'
(join_table_name: other_entity_key_name INTEGER NOT NULL)";
For some reason instead of entity id some its presentation is used.
The first problem is a hint for the second problem.
You need to use #MapKeyJoinColumn and not #MapKeyColumn.
http://docs.oracle.com/javaee/6/api/javax/persistence/MapKeyColumn.html
Specifies the mapping for the key column of a map whose map key is a
basic type
versus
http://docs.oracle.com/javaee/6/api/javax/persistence/MapKeyJoinColumn.html
Specifies a mapping to an entity that is a map key.

How to Map a Table with localized Product Names using JPA?

I have an entity product:
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "product")
#MapKeyColumn(name="locale")
public Map<String,ProductNames> getProductNames() {
return this.productNames;
}
and an entity named productNames which contains the localized versions of the product
private int id;
private SkiProduct skiProduct;
private String locale;
private String text;
I tried the #MapKeyColumn and the #MapKey annotation.
The generated SQL looks like this:
select
productnam0_.product_id as product4_1_,
productnam0_.id as id1_,
productnam0_.mapkey as mapkey1_,
productnam0_.id as id231_0_,
productnam0_.locale as locale231_0_,
productnam0_.product_id as product4_231_0_,
productnam0_.text as text231_0_
from
product_names productnam0_
My question is how to configure this mapping correctly. The mapkey column in the SQL-statement leads to a SQL-Errormsg since it is not in the db-table.
Type (ROW(id integer, locale char(2), text varchar(255), product_id integer)) not found.
EDIT: The Javadoc says: "If a persistent field or property other than the primary key is used as a map key then it is expected to have a uniqueness constraint associated with it. "
So I wonder whether I could use Maps for this ?
A Map key is supposed to be unique in Java, and that's reflected in JPA expecting a unique key in the database to map to the key for the Map.
If you can't guarantee that uniqueness, you need to use another datastructure or use another key.
If possible, set up a unique key on the fields you want to be the key, that should fix your problem (but you would possibly end up having to use a compound key, and thus a different Map structure in your Java code, for example you might have a mapkey+language_id compound key that's unique).

JPA: difference between #JoinColumn and #PrimaryKeyJoinColumn?

What's the exact difference between #JoinColumn and #PrimaryKeyJoinColumn?
You use #JoinColumn for columns that are part of a foreign key. A typical column could look like (e.g. in a join table with additional attributes):
#ManyToOne
#JoinColumn(name = "...")
private OtherClass oc;
What happens if I promote the column to be a/the PK, too (a.k.a. identifying relationship)? As the column is now the PK, I must tag it with #Id:
#Id
#ManyToOne
#JoinColumn(name = "...")
private OtherClass oc;
Now the question is:
Are #Id + #JoinColumn the same as just #PrimaryKeyJoinColumn?:
#ManyToOne
#PrimaryKeyJoinColumn(name = "...")
private OtherClass oc;
If not, what's #PrimaryKeyJoinColumn there for?
What happens if I promote the column to be a/the PK, too (a.k.a. identifying relationship)? As the column is now the PK, I must tag it with #Id (...).
This enhanced support of derived identifiers is actually part of the new stuff in JPA 2.0 (see the section 2.4.1 Primary Keys Corresponding to Derived Identities in the JPA 2.0 specification), JPA 1.0 doesn't allow Id on a OneToOne or ManyToOne. With JPA 1.0, you'd have to use PrimaryKeyJoinColumn and also define a Basic Id mapping for the foreign key column.
Now the question is: are #Id + #JoinColumn the same as just #PrimaryKeyJoinColumn?
You can obtain a similar result but using an Id on OneToOne or ManyToOne is much simpler and is the preferred way to map derived identifiers with JPA 2.0. PrimaryKeyJoinColumn might still be used in a JOINED inheritance strategy. Below the relevant section from the JPA 2.0 specification:
11.1.40 PrimaryKeyJoinColumn Annotation
The PrimaryKeyJoinColumn annotation
specifies a primary key column that is
used as a foreign key to join to
another table.
The PrimaryKeyJoinColumn annotation
is used to join the primary table of
an entity subclass in the JOINED
mapping strategy to the primary table
of its superclass; it is used within a
SecondaryTable annotation to join a
secondary table to a primary table;
and it may be used in a OneToOne
mapping in which the primary key of
the referencing entity is used as a
foreign key to the referenced
entity[108].
...
If no PrimaryKeyJoinColumn
annotation is specified for a subclass
in the JOINED mapping strategy, the
foreign key columns are assumed to
have the same names as the primary key
columns of the primary table of the
superclass.
...
Example: Customer and ValuedCustomer subclass
#Entity
#Table(name="CUST")
#Inheritance(strategy=JOINED)
#DiscriminatorValue("CUST")
public class Customer { ... }
#Entity
#Table(name="VCUST")
#DiscriminatorValue("VCUST")
#PrimaryKeyJoinColumn(name="CUST_ID")
public class ValuedCustomer extends Customer { ... }
[108] The derived id mechanisms
described in section 2.4.1.1 are now
to be preferred over
PrimaryKeyJoinColumn for the
OneToOne mapping case.
See also
Primary Keys through OneToOne Relationships
This source http://weblogs.java.net/blog/felipegaucho/archive/2009/10/24/jpa-join-table-additional-state states that using #ManyToOne and #Id works with JPA 1.x. Who's correct now?
The author is using a pre release JPA 2.0 compliant version of EclipseLink (version 2.0.0-M7 at the time of the article) to write an article about JPA 1.0(!). This article is misleading, the author is using something that is NOT part of JPA 1.0.
For the record, support of Id on OneToOne and ManyToOne has been added in EclipseLink 1.1 (see this message from James Sutherland, EclipseLink comitter and main contributor of the Java Persistence wiki book). But let me insist, this is NOT part of JPA 1.0.
I normally differentiate these two via this diagram:
Use PrimaryKeyJoinColumn
Use JoinColumn
I know this is an old post, but a good time to use PrimaryKeyColumn would be if you wanted a unidirectional relationship or had multiple tables all sharing the same id.
In general this is a bad idea and it would be better to use foreign key relationships with JoinColumn.
Having said that, if you are working on an older database that used a system like this then that would be a good time to use it.
You use #JoinColumn when you want to manage (change the column name, set nullable and so on) the foreign key column in the target entity table. Here, the Address table will contains User table id like foreign key but the column it's will be name user_id (the second scheme of #Sam YC)
#Entity
public class Address implements Serializable {
#Id
#GeneratedValue
private String id;
private String city;
#OneToOne(optional = false)
#JoinColumn(name = "user_id", updatable = false)
private User user;
}
You use #PrimaryKeyJoinColumn, when you want to use the primary key of the referencing entity like the target entity primary key. Here the Address know the referencing User but Address table hasn't foreign key column, because it's has the same id than User Id (the first scheme of #Sam YC)
#Entity
public class Address implements Serializable {
#Id
#GeneratedValue(generator = "foreignKeyGenerator")
#GenericGenerator(
name = "foreignKeyGenerator",
strategy = "foreign",
parameters = #org.hibernate.annotations.Parameter(
name = "property", value = "userT"
)
)
private String id;
private String city;
#OneToOne(optional = false)
#PrimaryKeyJoinColumn
private User userT;
}

Categories