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?
Related
I am trying to add an #ElementCollection but the column is not found after the setup, so I constantly receive an error. I use Spring + flyway for the set up. Everything happens in the public schema
So here is my big object:
#Entity
#Table(name = "my_big_table")
MyBigObject{
#Id
#Column(name=COL_ID)
#GeneratedValue(generator="gen_name")
#GenericGenerator(
name = "gen_name",
strategy = "seq_name"
)
#AttributeAccessor(CommonConstants.HIBERNATE_ACCESS_PROPERTY)
private long id;
...
...
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(
name = "my_small_table",
joinColumns = #JoinColumn(name = "big_object_id")
)
private List<MySmallObject> mySmallObjects;
}
Here is my embedded object:
#Embeddable
public class MySmallObject {
#Column(name = "small_object_type")
private String smallObjectType;
}
Then besides the existing my_big_table table I add my_small_table using flyway
create table if not exists my_small_table
(
big_object_id bigint not null,
small_object_type varchar(64) not null
);
alter table my_small_table
add constraint FK_my_small_table
foreign key (big_object_id)
references my_big_table (id);
After this the my_small_table is successfully created but any instance of MyBigObject cannot be found because it looks for a column in the my_small_table that does not exist. As you can see it does not understand that the column name should use an underscore.
Big error trace ands with the following message:
Caused by: org.postgresql.util.PSQLException: ERROR: column mysmalltab0_.smallobjecttype does
not exist
09:17:24.994 INFO - STDOUT: Hint: Perhaps you meant to reference the column "mysmalltab0_.smallobjecttype".
Do you know what I could forget? Could lombock annotations that I also use for both classes spoil the picture?
As it's stated in the documentation:
By default, the placement of the #Id annotation gives the default access strategy. When placed on a field, Hibernate will assume field-based access. When placed on the identifier getter, Hibernate will use property-based access.
But the usage of the #AttributeAccessor leads to the changing access strategy for the field that hold #Id and as result your #Column(name = "small_object_type") annotation just was ignored. You can try to put it on the appropriate getter and it should work. But it's considered a good practiŃe not to mix up access strategies for the entity fields.
I'm having some problems with my PostgreSQL database columns on some tables.
If I declare a column of String with a name like a_column_name Hibernate accepts the column of the table with the name a_column_name.
But my problem comes when I declare the column type to Integer or BigDecimal. At start, Hibernate creates a new column of type int4 or numeric(19, 2) with a name like acolumnname with NULL's.
Hibernate: alter table t_myTable add column acolumnname int4
or
Hibernate: alter table t_myTable add column acolumnname numeric(19, 2)
I've tried to set the name-strategy on the config file of Spring boot:
jpa:
show-sql: true
hibernate:
ddl-auto: update
properties:
hibernate.cache.use_second_level_cache: false
hibernate.cache.use_query_cache: false
hibernate.generate_statistics: true
naming.physical-strategy: PhysicalNamingStrategyStandardImpl
But with no result...
So my question is why Hibernate accepts the column name when it's of String type but doesn't like it when it's Integer or BigDecimal.
Regards.
---- Update 1 ----
After search the Entity to post here the code as Veselin Davidov asked, I've noticed that some columns are accepted while others not.
The entities are created from a company framework that parses a JSON with the DB structure (tables, fields, type of fields...) and generates the JAVA entity class. So after see the answer from SAM I've changed the code of the Entity template to add a #Column(name = "your_column_name"). Now, when Hibernate starts, it doesn't add the columns with the wrong names and uses the DB columns.
if your Hibernate entity class is like the following:
#Entity
public class Registration{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Column
#Basic
private String name;
// getters and setters
}
then your registration table will have fields named with "id" and "name" as you have used auto-ddl=update. To avoid that you can specify the column names with #Column(name = "your_column_name") like below:
#Table(name = "registration")
#Entity
public class Registration{
#Id
#Column(name = "r_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Basic
#Column(name = "r_name")
private String name;
// getters and setters
}
If you are using Intellij IDEA then the IDE can generate the required Entity class.
You can go to your persistence tab -> right-click on your project name -> Generate Persistence Mapping -> By Database Schema. Then select the tables whose Entity you want to generate. Voila everything comes with ease.
Now coming to your problem, in your Entity class if you set some filed with Integer type then it will update your table to int4 and with allow null. The BigDecimal is also same numeric(10,2) with allow null. To avoid allow null in your database use primitive type int and double.
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.
In Spring JPA I havean entity and I init the schema using FlywayDb.
My entity is:
#Entity
#Table(schema = "scheduler",
uniqueConstraints={#UniqueConstraint(name = "uq_task", columnNames = {"task", "date_at"})}
)
public class Task {
#Id
private Long id;
#Embedded
#Column(nullable = false)
private ITask task;
#Column(nullable = false)
private Date dateAt;
}
The schema is initialized as follows:
CREATE SCHEMA scheduler;
CREATE TABLE scheduler.task (
id bigserial primary key,
task bytea NOT NULL,
date_at timestamp NOT NULL
);
CREATE UNIQUE INDEX uq_task
ON scheduler.task(task, date_at);
Without the constraints on the entity, it works, with it doesn't. In particular I have the exception:
Caused by: org.hibernate.AnnotationException: Unable to create unique key constraint (task, date_at) on table task: database column 'task' not found. Make sure that you use the correct column name which depends on the naming strategy in use (it may not be the same as the property name in the entity, especially for relational types)
at org.hibernate.cfg.Configuration.buildUniqueKeyFromColumnNames(Configuration.java:1684)
at org.hibernate.cfg.Configuration.buildUniqueKeyFromColumnNames(Configuration.java:1616)
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1452)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1846)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:857)
I use an H2 database.
ITask is an interface with several POJO implementations. ITask interface is annotated with #Embeddable.
My guess is that JPA tries to apply the unique constraints on columns that are not yet created by FlywayDb library. But this makes no sense to me.
Any idea?
After update of you question now I can guess that there is a problem with attribute in your ITask insterface please read that doc. In my opinion you have to override embbedable entity attribute to fix your problems.
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.