Field annotated with #Transient being persisted in #DataJpaTest - java

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.

Related

About inheritance mapping and best practices in Java

I'm learning about the ways of mapping inheritance from database to java with JPA/Hibernate. I've found several examples of how to do it, but not how to apply it.
Now, I'm trying to apply this knowledge on a small project, but I run into a problem where I can't do it the way I thought it would be ideal.
About the code below, the problem is: I have an "Expense" class that records a new expense (credit card debt, etc.), this debt has a creditor, which can be a person (PF) or institution (PJ). A expense has only one creditor, but I'm forced to model with one of each subclass.
#Data
#Entity
#Table(name = "expense")
public class Expense {
// CODE
#ManyToOne
#JoinColumn(name = "creditorPF")
private CreditorPF creditorPF;
#ManyToOne
#JoinColumn(name = "creditorPJ")
private CreditorPJ creditorPJ;
}
#Data
#Entity
#Table(name = "creditor")
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class Creditor {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "idCreditor")
protected Long id;
#NonNull
protected String description;
}
#Getter
#Setter
#Entity
#Table(name = "creditor_pf")
#PrimaryKeyJoinColumn(name = "idCreditor")
public class CreditorPF extends Creditor {
private String cpf;
#Builder
public CreditorPF() {
super("");
}
#Builder
public CreditorPF(String cpf, String nome) {
super(nome);
this.cpf = cpf;
}
}
#Getter
#Setter
#Entity
#Table(name = "creditor_pj")
#PrimaryKeyJoinColumn(name = "idCreditor")
public class CreditorPJ extends Creditor {
private String cnpj;
#Builder
public CreditorPJ(String cnpj, String nome) {
super(nome);
this.cnpj = cnpj;
}
#Builder
public CreditorPJ() {
super("");
}
}
This works fine, but I don't think it's a good design, because the design is allowing one more creditor per subclass, even if I add validations to prevent it, the design would be semantically incorrect.
Is there a way I can get a design like this code below, but that I can get the subclass information when I retrieve the object through hibernate?
#Data
#Entity
#Table(name = "expense")
public class Expense {
// CODE
#ManyToOne
#JoinColumn(name = "creditor")
private Creditor creditor;
}
In case of getting the Credit type from THROUGH the Expense Object ,there's no way besides using instanceof or getClass() as #Chris said ,btw it's preferable to use composition over inheritence,since it doesn't introduce this problem and preserves database consistency since you can"t be FORCED to have nullable fields, and in your case you can implement it using a class Creditor containing an enum which holds the creditor type since it's know to you ,hope this helps !

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;

Java Double Brace Initialization causes IllegalArgumentException: Unknown entity

I just try to create a CRUD Web Application with Spring Boot and I found that there is a problem with using Java Double Brace Initialization in the framework.
Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: Unknown entity: com.example.service.impl.FileImageServiceImpl$1; nested exception is java.lang.IllegalArgumentException: Unknown entity:
I have the #Entity class:
#Entity
public class RandomEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
//Getter and Setter
}
A #RestController
#RestController
public class RandomController{
#Autowired
private RandomRepository randomRepository;
#GetMapping("/create")
public String create(){
RandomEntity rdEntity = new RandomEntity(){{
setName("Bla Bla");
}};
return randomRepository.save();
}
}
Here is the repository
public interface RandomRepository extends CrudRepository<RandomEntity, Long> {
}
But when I change Java Double Brace Initialization to Normal Initialization, the Application run properly.
Do you know why is that?
Thank you so much!
It may look like a nifty shortcut that just calls the constructor of your class followed by some initialization methods on the created instance, but what the so-called double-brace initialization really does is create a subclass of your Entity class. Hibernate will no longer know how to deal with that.
So try to avoid it. It has a lot of overhead and gotchas just to save you a few keystrokes.
I just want to complete the answer of #Thilo, If you want a clean code use Builder design pattern, now you can implement this Design easily via Lombok library, so you can Just annotate your Entity like so :
#Entity
#Getter #Setter #NoArgsConstructor #AllArgsConstructor
#Builder(toBuilder = true)
class RandomEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
So there are really some cool annotations, for example #Getter and #Setter to avoid all that getters and setters, #Builder(toBuilder = true) to work with builder design so your controller can look like :
#GetMapping("/create")
public RandomEntity create() {
// Create your Object via Builder design
RandomEntity rdEntity = RandomEntity.builder()
.name("Bla Bla")
.build();
// Note also here save should take your Object and return RandomEntity not a String
return randomRepository.save(rdEntity);
}

Hibernate returning null for one to many relation if junit test is wrapped transactional

I have a one to many relation where the "children" are fetched lazy. If running a junit test with this setup i will run into a org.hibernate.LazyInitializationException - i've looked for tons of possible solutions (I am not willing to set fetch type to eager only to test stuff more easy)
However, if I annotate my test as #Transactional I will not get the error, but it will return null for the entities children.
I somehow understand the lazy fetch exception, but if i'm within the same transaction, why can't i lazy fetch the stuff? Example:
#Transactional
#Test
public function test() {
ParentEntity parentEntity = parentEntityService.read(requestContext, p.getId());
Set<ChildEntity> childEntitites = parentEntity.getChildren();
Assert.assertNotNull(childEntities); // will fail
}
The same without #Transactional will cause lazy fetch exception. I've already treid using
Hibernate.initialize() on the parentEntity after raeding it
getting an instance of entityManager and opening and closing transactions manually
Any ideas?
** EDIT **
#Entity
#Table(name = "T_PARENTENTITY")
#SequenceGenerator(initialValue = 1, name = "idgen", sequenceName = "SEQ_PARENTENTITY")
public class ParentEntity extends BaseEntity{
#Getter #Setter
#NotNull #NotEmpty
#Column(name="STRING_1")
private String string1;
#Getter #Setter
#OneToMany(orphanRemoval = true)
#JoinColumn(name="PARENTENTITY_ID", updatable = false)
#JsonView(View.DetailView.class)
private Set<ChildEntity> children;
}

Categories