saveAndFlush in Spring data JPA - java

#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.

Related

Field annotated with #Transient being persisted in #DataJpaTest

I'm practicing TDD,
So now I'm trying to make a test that will fail for now. About to test a #Entity that don't have a field relationship mapped so far. So I'm expecting my test to fail.
Here is TableA entity, you may notice that the TableB relationship is annotated with #Transient, so this field does not get persisted and don't get errors when running other integration tests ( tests that uses #RunWith(SpringRunner.class).
#Builder
#Table(name = "table_a")
#Entity
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class TableAData {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Setter
private String name;
#Transient
#Builder.Default
private List<TableBData> tableBs = List.of();
}
Here is the code for TableB entity, nothing really interesting about it.
#Getter
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "table_b")
public class TableBData {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Setter
private String name;
}
I also have a TableAJpaRepository, this extends JpaRepository<TableAData, Long>:
public interface TableAJpaRepository extends JpaRepository<TableAData, Long> {
public Optional<TableAData> findByName(String name);
}
My question is:
Why does the follow test is not falling?
#DataJpaTest
#RunWith(SpringRunner.class)
public class TableAJpaRepositoryIntegrationTest {
#Autowired
private TableAJpaRepository repository;
#Test
public void dataFechedByIdWhenGetTableBsShouldBringTableB() {
TableBData tableBItem = TableBData.builder()
.name("123 Test")
.build();
TableAData tableAItem = TableAData.builder()
.name("TableAEntryName")
.tableBs(List.of(tableBItem))
.build();
Long id = repository.save(archetype).getId();
repository.flush();
TableAData fetched = repository.getOne(id);
assertThat(fetched.getTableBs()).isNotEmpty(); // This should be falling
assertThat(fetched.getTableBs().get(0).getName()).isEqualTo("123 Test");
}
}
Looks like getTableBs method is returning the other table entity from relationship, but I don't have it mapped. Am I missing something?
So my friend and I spent some time trying to figure out what was going on. We've found this github issue that describes exactly the same issue. The person who open the issue also create a repo with a minimum reproducible example.
One other thing that helped a lot was this SO answer: You fell in the trap of JPAs first level cache.
Looks like it's because of a cache.
To summarize, the solution was:
Inject TestEntityManager to persist and setup test scenario.
Always using TestEntityManager.persistAndFlush() method.
Call TestEntityManager.clear() before starting tests.
Repositories was used normally in test cases.

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.

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;

Failed to lazily initialize a collection of role. Simple JPA findById

I'm using Spring and JPA (Hibernate with MySQL) and Lombok also.
Hi have this part of my entities:
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "entitya")
public class EntityA implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="ea_id")
Long id;
....
#ManyToOne
#JoinColumn(name="g_id", nullable=false)
private Group group;
....
}
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "group")
public class Group implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="g_id")
private Long id;
#OneToMany(mappedBy="group")
private List<EntityA> enitiesA = new ArrayList<>();
...
}
I implemented also the repository extends JPARepository.
Into my controllers, if I try to retrieve an EntityA by Id I obtain this exception:
failed to lazily initialize a collection of role: com.mytest.entity.Group.enitiesA, could not initialize proxy - no Session
For me it's strange because I need to retrieve only the object. I not use some getter methods on this. So, in theory, using the default fetch types, I don't need to have also the group list.
What's wrong?
Are you debugging your object with toString()?
In case it could be an error caused by the #Data annotation.
The generated toString() method contains all fields, so it might call the enitiesA variable, producing the lazy initialization error.
https://mdeinum.github.io/2019-02-13-Lombok-Data-Ojects-Arent-Entities/
Likely it's because you're accessing group.enitiesA outside of the transactional boundaries. If you want to do this, you can eager fetch them by adding eager fetch type to your OneToMany mapping such as
#OneToMany(mappedBy="group", fetch = FetchType.EAGER)
This will load the entire object graph when the parent is loaded.
If you still want to do lazy loading, look to encapsulate all of the calls into the children under the session that loaded the parent.

Hibernate: how to insert foreign key id to a table

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());
*/
}

Categories