Hibernate: how to insert foreign key id to a table - java

I am new to Hibernate and I am trying to save List<AuditScope> auditScopes from Audit entity using Hibernate. Audit entity has One-to-Many relationship with AuditScope entity, Audit can have many AuditScope. I am using saveAll() method to batch insert auditScopes instead of saving one by one through a loop. Unforunately, I have to loop auditScopes just to set each AuditScope's auditId(FK) manually instead of automatically. What I want is to batch insert them without looping and manually setting auditId. I am sorry my code is not good. Thank you very much.
Here are my classes:
#Entity
#Table(name="audit")
public class Audit {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="audit_gen")
#SequenceGenerator(name="audit_gen")
#Getter #Setter private int auditId;
#Getter #Setter private String auditName;
#Transient #Getter #Setter private List<AuditScope> auditScopes;
}
#Entity
#Table(name="auditScope")
public class AuditScope {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="auditScope_gen")
#SequenceGenerator(name="auditScope_gen")
#Getter #Setter private int auditScopeId;
#Getter #Setter private int auditId; //FK
#Getter #Setter private String scope;
}
Here's my insertAudit() method
public void insertAudit(Audit audit){
//Save Audit first to get auditId
Audit theAudit = auditRepository.save(audit);
//Loop audit.getAuditScopes() to manually set auditId
for(AuditScope scope : audit.getAuditScopes()){
scope.setAuditId(theAudit.getAuditId());
}
//Save all AuditScope with set auditId
auditScopeRepository.saveAll(audit.getAuditScopes());
/*
Expected result
auditRepository.save(audit);
auditScopeRepository.saveAll(audit.getAuditScopes());
*/
}

Related

DB column name generation problem (missing column) when OneToMany is in the abstract class

Situation: Our application has been working properly with all the OneToMany associations, with the names, columns. We decided out of nowhere that we want our entity objects to change names just by adding DAO at the end. Object -> ObjectDAO.
What we did: We changed object names as planned. Then every entity got its #Table(name = "object") annotation, but now we are running into problems on our join tables. The names of columns are now generated badly - not using the given table's name. We want the column names to remain object_id instead of objectdao_id but #Table annotation does not do the trick.
nested exception is org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing column [great_objectdao_id] in table [our_join_table]
I suppose column name generation was always taking into consideration not a table name, but object name then. What's making things harder, is our scheme where we have a BaseDAO and BaseObjectDAO objects inside of which the #OneToMany relations exist.
SomeOtherDAO object would have a some_other table name.
#Data
#MappedSuperclass
public abstract class BaseDAO {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonIgnore
private Long id;
(...)
}
#EqualsAndHashCode(callSuper = true, exclude = {"objectSources"})
#NoArgsConstructor
#Data
#MappedSuperclass
public abstract class BaseObjectDAO extends BaseDAO {
#JsonIgnore
#OneToMany
private Set<SomeOtherDAO> objectSources;
(........)
}
How do the objects extending BaseObjectDAO look like:
#EqualsAndHashCode(callSuper = true)
#Entity
#Table(name = "great_object")
#Data
#NoArgsConstructor
#Accessors(chain = true)
public class GreatObjectDAO extends BaseObjectDAO {
#EqualsAndHashCode(callSuper = true)
#Entity
#Table(name = "strange_object")
#Data
#NoArgsConstructor
#Accessors(chain = true)
public class StrangeObjectDAO extends BaseObjectDAO {
Question:
How do we force it to generate join tables with column names great_object_id and strange_object_id instead of great_objectdao_id and strange_objectdao_id?
The great_object table should remain to have the id column, and the change should only be visible in the join table.
join_table table column names we have and want to keep:
great_object_id, object_sources_id
You must use the #AttributeOverride annotation to change the name of the id column of the subclasses. See https://docs.oracle.com/javaee/6/api/javax/persistence/AttributeOverride.html
#EqualsAndHashCode(callSuper = true)
#Entity
#Table(name = "great_object")
#Data
#NoArgsConstructor
#Accessors(chain = true)
#AttributeOverride(name="id", column=#Column(name="great_object_id"))
public class GreatObjectDAO extends BaseObjectDAO {

org.hibernate.MappingException: Unknown entity - Hibernate, SpringBoot, DTO pattern

I am trying to build a simple SpringBoot and Hibernate app using DAO and DTO pattern.
I am trying to save a list of users to the database.
When I am using User class it works fine, but when I am trying to use DTO CreateUserDto class I am getting the following error:
"Unknown entity: com.app.sportapp.dto.CreateUserDto; nested exception is org.hibernate.MappingException: Unknown entity: com.app.sportapp.dto.CreateUserDto"
There is a SingleTable inheritance where Player class and Coach class inherit User class.
User.java
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Getter
#Setter
#Entity(name = "Users")
#ApiModel(description = "All details about user")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "User_Type", discriminatorType= DiscriminatorType.STRING)
public class User implements Seriaalizable{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
private String username;
private String email;
private String password;
private String contactNumber;
}
Player.java
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Getter
#Setter
#Entity(name = "Players")
#DiscriminatorValue(value = "player")
#DiscriminatorOptions(force=true)
public class Player extends User {...}
Coach.java
#Entity(name = "Coaches")
#DiscriminatorValue(value = "coach")
#DiscriminatorOptions(force=true)
public class Coach extends User{
}
And here are DTO's:
CreateUserDto.java
public class CreateUserDto {...}
PlayerDto.java
public class PlayerDto extends CreateUserDto{...}
CoachDto.java
public class CoachDto extends CreateUserDto{
}
As I am very new to DAO and DTO pattern from error I am getting I assume that it is expected to have a model with #Entity called CreateUser so same name as DTO CreateUserDto? Or can I have the example what I did to have a User model and create a new CreateUserDto?
Thanks!
The error happens because you are treating a DTO as an entity.
Remove the JPA annotations from the DTOs and don't use those classes for connecting to the db.
You will convert the results from your queries from entities to DTO and vice-versa.
I would also suggest to have a look at Mapstruct for the creation of DTO. This will probably make it easier to separate the entities from the DTOs.

saveAndFlush in Spring data JPA

#Entity
#Table(name = "DISC")
#NoArgsConstructor
#AllArgsConstructor
#Data
#Builder
#ToString
#EqualsAndHashCode(of = { "discId" })
public class Disc implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#ReturnInsert
#Column(name = "DISC_ID")
private Long discId;
..
}
and
#Repository
public interface DiscRepository extends JpaRepository<Disc, Long> {
...
}
but when I save using saveAndFlush() I have this error:
org.springframework.orm.jpa.JpaSystemException: ids for this class must be manually assigned before calling save():
Looks like you don't set the discId field, as simple as that.
If you want to delegate this to the framework use #GeneratedValue (and set strategy which corresponds to your DB). The framework will handle the ids generation for you.

Map string properties to JSONB

I've been trying map my string properties to Postgresql's JSONB using JPA. I did read perfect article by Vlad Mihalcea many times and also seen relative questions and problems with similar stuff. BUT I still have this exception org.postgresql.util.PSQLException: ERROR: column "json_property" is of type jsonb but expression is of type character varying every time when I'm trying insert something into my table.
And what even worse is - all these advices in similar questions were useful until I changed my entity class and made him inherits super class. And now situation is like this:
If #TypeDef and #Type on my child class and it works great
But I want use abstraction layer and set annotations, which I noticed above, to my base entity class and after that exception says me 'Hello! It's me again'
My hierarchy is pretty simple and here it is:
Base entity
#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
#MappedSuperclass
public abstract class AbstractServiceEntity implements Serializable {
private Integer id;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
Child entity
#Entity
#Table(schema = "ref", name = "test_json_3")
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
public class TestJson extends AbstractServiceEntity {
#Type(type = "jsonb")
#Column(columnDefinition = "jsonb")
private String jsonProperty;
My table
create table ref.test_json_3
(
id serial primary key,
json_property jsonb
)
UPD
I've succesfully inserted record with JPA native query, but I had to unwrap my query into hibernate query. Not sure that it's the most convinient way to manage inserting data into DB. The my question is actual, I still need your help) Example with native query below.
Code snippent with result
#Repository
public class JpaTestRepository {
#PersistenceContext
private EntityManager entityManager;
#Transactional
public void insert(TestJson testJson) {
entityManager.createNativeQuery("INSERT INTO test_json_3 (json_property) VALUES (?)")
.unwrap(Query.class)
.setParameter(1, testJson.getJsonProperty(), JsonBinaryType.INSTANCE)
.executeUpdate();
}
Finally I found solution for my problem. Answer is - just use your #Column(columnDefinition = "jsonb") and #Type(type = "jsonb" via getters but not class properties.
entity definition
#Entity
#Table(schema = "ref", name = "test_json_3")
#NoArgsConstructor
#AllArgsConstructor
#Setter
public class TestJson extends AbstractServiceEntity {
private String jsonProperty;
#Type(type = "jsonb")
#Column(columnDefinition = "jsonb")
public String getJsonProperty() {
return jsonProperty;
}
You can try to add #TypeDefs under class TestJson:
#TypeDefs({
#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
})
public class TestJson extends AbstractServiceEntity {
Alternate solution for mapping String to Jsonb type. Just add the following annotation on your string.
#ColumnTransformer(write = "?::jsonb")
private String jsonProperty;

Spring Data + JPA > New entries of "mappedBy" property are not fetched from DB after SQL INSERT

Hej.
I think I am experiencing some odd behaviour with Spring Data, JPA and the alike. I wrote a flyway migration script that inserts new data into a table cash_entry_note.
Which is related to an entity AbstractCashEntry the following way:
#Entity
#Table(name = AbstractCashEntry.TABLE_NAME)
#Inheritance(strategy = InheritanceType.JOINED)
#Data
#ToString(callSuper = true)
#EqualsAndHashCode(of = "key", callSuper = false)
#NoArgsConstructor
abstract public class AbstractCashEntry extends TenantBaseModel {
#OneToMany(mappedBy = "cashEntry", cascade = { CascadeType.ALL })
protected Set<CashEntryNote> notes = new HashSet<>();
which is the super class for CashEntry
#Entity
#Table(name = "cash_entry")
#Data
#ToString(callSuper = true, exclude = "group")
#EqualsAndHashCode(callSuper = true)
#NoArgsConstructor
public class CashEntry extends AbstractCashEntry {
and finally the class CashEntryNote itself
#Entity
#Table(name = "cash_entry_note")
#Data
#ToString(callSuper = true, exclude = "cashEntry")
#EqualsAndHashCode(callSuper = true)
#NoArgsConstructor
#AllArgsConstructor
public class CashEntryNote extends BaseModel {
#ManyToOne(optional = false)
private AbstractCashEntry cashEntry;
#ManyToOne(optional = false)
private Note note;
#ManyToOne(optional = false)
#DiffIgnore
private User reporter;
public LocalDateTime getDate() {
return createdAt;
}
}
now what I do is some plain and simple SQL inserts:
INSERT INTO [cash_entry_note] (created_at, modified_at, cash_entry, note, reporter) VALUES (GETDATE(), GETDATE(), #cash_key, #initial_note, 'username')
After that, I would expect the JpaRepository to return the CashEntry entity with the attached new notes, I just recently inserted. However, this is not the case. The property notes of a CashEntry is always an empty set or contains the notes that where already there prior to the SQL inserts. Even when trying to use FetchType.EAGER or restarting the server, the repository does not return the newly sql-inserted notes. What am I missing? Anybody got an idea?
I should also mention, that this project is using [Javers][1] http://javers.org/documentation/ to track changes in entites and provide them later on in an audit log.
Got it. The #cash_key variable somehow contained spaces or a tab or any invisible control characters - which however did not prevent it to work in a comparison in a where clause before the insert, so that was really hard to pinpoint.
SELECT #cash_key = min(cash_key) FROM cash_entry is to blame here

Categories