lunes, 20 de junio de 2022

Spring Data Jpa Entity RelationShip Mappings @OneToOne Unidirection

Spring Data Jpa Entity RelationShip Mappings 

@OneToOne Unidirection




@OneToOne Unidirectional




Java Entities

Capital Entity
@Entity
@Data
@NoArgsConstructor
@Table( name = "capital", schema = "public")
public class Capital {
@Id
@GeneratedValue(strategy = GenerationType.AUTO, generator = "public.capital_seq")
private Long id;
private String name;

public Capital(String name) {
this.name = name;
}
}
Country Entity

@Entity
@NoArgsConstructor
@Table(name = "country", schema = "public")
public class Country {
@Id
@GeneratedValue(strategy = GenerationType.AUTO, generator="public.country_seq")
private Long id;
private String name;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "capital_id_fk", // default "item_id", could by omitted
referencedColumnName = "id") // default could be omitted
private Capital capital; // in DB -> item_id_fk bigint H2


Hibernate creates automatically the tables the next way

Tables

    create table public.capital (
       id int8 not null,
        name varchar(255),
        primary key (id)
    )

    create table public.country (
       id int8 not null,
        name varchar(255),
        capital_id_fk int8,
        primary key (id)
    )

Test the behaviour

Country country = new Country("Mexico");
Capital capital = new Capital("CDMX");
country.setCapital(capital);
Country _country = countryRepository.save(country);
log.info("savedCountry {}", _country);

Country countryFinded = countryRepository.findById(_country.getId()).get();
log.info("recoveredCountry: {}", countryFinded);

Capital recoveredCapital = capitalRepository.findById(_country.getCapital().getId()).get();
log.info("recoveredCapital: {}", recoveredCapital);

// given new Country , new Capital -> capital set country IMPOSSIBLE -> save capital
// never save Country

List<CountryCapital> countryCapitalList = countryRepository.getJoinCountryCapital();
countryCapitalList.forEach(e -> log.info("countryCapital: {}", e));
On the output the queries generated by hibernate on the test.

Creation of tables

Hibernate: 
    
    create table public.capital (
       id int8 not null,
        name varchar(255),
        primary key (id)
    )
Hibernate: 
    
    create table public.country (
       id int8 not null,
        name varchar(255),
        capital_id_fk int8,
        primary key (id)
    )
Hibernate: 
    
    alter table if exists public.country 
       add constraint FK54ksb3rb0668rqk7cgvo7tab7 
       foreign key (capital_id_fk) 
       references public.capital

Queries executed by test

Hibernate: 
    select
        nextval ('public.country_seq')
Hibernate: 
    select
        nextval ('public.capital_seq')
Hibernate: 
    /* insert com.bext.onetooneunidirection.entity.Capital
        */ insert 
        into
            public.capital
            (name, id) 
        values
            (?, ?)
Hibernate: 
    /* insert com.bext.onetooneunidirection.entity.Country
        */ insert 
        into
            public.country
            (capital_id_fk, name, id) 
        values
            (?, ?, ?)
2022-06-20 19:11:32.881  INFO 19960 --- [           main] .o.SpringdatajpaentitymappingApplication : savedCountry Country{id=1, name='Mexico', capital=Capital(id=1, name=CDMX)}
Hibernate: 
    select
        country0_.id as id1_1_0_,
        country0_.capital_id_fk as capital_3_1_0_,
        country0_.name as name2_1_0_,
        capital1_.id as id1_0_1_,
        capital1_.name as name2_0_1_ 
    from
        public.country country0_ 
    left outer join
        public.capital capital1_ 
            on country0_.capital_id_fk=capital1_.id 
    where
        country0_.id=?
2022-06-20 19:11:32.902  INFO 19960 --- [           main] .o.SpringdatajpaentitymappingApplication : recoveredCountry: Country{id=1, name='Mexico', capital=Capital(id=1, name=CDMX)}
Hibernate: 
    select
        capital0_.id as id1_0_0_,
        capital0_.name as name2_0_0_ 
    from
        public.capital capital0_ 
    where
        capital0_.id=?
2022-06-20 19:11:32.904  INFO 19960 --- [           main] .o.SpringdatajpaentitymappingApplication : recoveredCapital: Capital(id=1, name=CDMX)
Hibernate: 
    /* SELECT
        new com.bext.onetooneunidirection.dto.CountryCapital(c.name,
        i.name) 
    FROM
        Country c 
    JOIN
        c.capital i */ select
            country0_.name as col_0_0_,
            capital1_.name as col_1_0_ 
        from
            public.country country0_ 
        inner join
            public.capital capital1_ 
                on country0_.capital_id_fk=capital1_.id
2022-06-20 19:11:32.922  INFO 19960 --- [           main] .o.SpringdatajpaentitymappingApplication : countryCapital: CountryCapital(countryName=Mexico, capitalName=CDMX)


============================================================



@JoinColumn to Specify the "JoinColumn"

public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.AUTO, generator="customer_seq")
private Long id;
private String name;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name="fk_item_id")
private Item item;
Table generated

create table customer (
       id bigint not null,
        name varchar(255),
        fk_item_id bigint,
        primary key (id)
    )


-ToOne relationship, JPA specifies a default of FetchType.EAGER, better put ot Lazy.

-ToMany relationships. Better use pagination.

Bidirectinal associations requiere auxiliar operations when add and remove. 
Order class
public void addItem(Item item){
   item.setOrder(this);
   this.items.add(item);
}

public void removeItem(Item item){
   item.setOrder(null);
   this.items.remove(item);
}

@OneToOne bidirectional

- Uning really @OneToMany bidirectional
- Using Shared Key

Using SharedKey not specified to do in spring yet.

In bidirectional associations needs to be care of update, the add or remove needs to perform the operation on both ends.

@OneToMany unidirectional

this should be avoided, Hibernate might crate unexpected tables and execute more sql statementes than you expected. with @JoinColumn skip the creation of extra table, but still create n+1 query, 2 select, insert, update. even with a JOIN FETCH query. 
  Is prefered to use @OneToMany bidirectional. Better to use ManyToOne unidirectional because Hibernate when initialize -ToMany loads all the associations.

https://www.bezkoder.com/jpa-many-to-many/

joining multiple entities in Spring JPA
https://stackoverflow.com/questions/70136727/joining-multiple-entities-in-spring-jpa

JPA cascade PERSIST   Employee --- Address
If not specified, One must to save address and Employee, Else only save parent Employee and their soons will be saved automatically.

Thorben Janssen: JPA vs Hibernate: The differece between save, persist, merge and update
https://www.youtube.com/watch?v=RVlSVPATJio&list=PL50BZOuKafAbXxVJiD9csunZfQOJ5X7hP&index=8&ab_channel=ThorbenJanssen

                                                       Detached

                                                       |                detached(entity)
                                       merge(entity)            evict(entity)
                                       update(entity)           clear()
                                                       |                  close()
                                                       |                   ^                             find(class,id)
                                                       V                  |                             get(class,id)
                                                                                                        load(class,id)               DB
Transient    persist(entity) -->      Managed                                     all queries
                   save(entity)         persistent context     <------------------------------------- 
                                                 1st level cache                                 
                                                      |         ^                -- flush() ------------------------->
                                                      |          |
                      remove(entity)        |          |      persist(entity)
                                                      |          |      save(entity)  
                                                      V        |
                                                     Removed

JPA.persist or Hibernate.save 
JPA.persist return void  | Hibernate.save return primary key

Hibernate primary key value to store  the entity in the first level cache. generates the primary key immediatley and (execute SQL trigger if necesary) when you call save or persist method. This is not
when uses IDENTITY strategy without and Active Transaction or with FlushMode.Manual. 
When Persist Method: In this case hibernate delays the exccution of SQL Insert and creates a temporatily primary key value.
When Save Method: hibernate excetues the SQL Insert immediately and retrieves the primary key from database.

strategy.generationType.TABLE requiere row level Locks on the primary key table and doesn't scale well.
    
JPA.merge copies the state of a detached entity to a managed instance of the same entity. 
Hibernate instead executes SQL select to retrieve the managed entity to the database. if the persistence context already contained a managed instance, Hibernate uses the existing one instead. it the copies all the attributes values to the managed entity and returns it to the caller. This way Hibernate lost the change made for the entity because use the existing one.
In contrast to JPA´s merge you can´t lose any changes by calling the update method. If the persistence  context already contains a managed instance of the entity you want to update, it throws an exception.
Hibernate update just perform the SQL Update, doesn´t perfomr any dirty check.
This could be a problem if your DBA registered an update Trigger for the database table. To perform a dirty check (SQL Select) annotate the entity with @SelectBeforeUpdate. Now the behavior of update method is similar to the JPA´s merge method.
The difference between Update method, Hibernate only select  the entity which you provided as a method parameter. JPA's Merge method Hibernate also select all the association with CascadeType.MERGE. 
  So select JPA Merge method if you reattach a huge graph of entities.

What to use Update or Merge.
            - do you need dirty checks?
merge - Do you reattach a graph of entities?


Derived Queries with Spring Data JPA

  -  Use @Query(" ...  ") to define custom query

  - Method Start with 
       - findBy...(...)
       - readBy...(...)
       - queryBy...(...)
       - getBy...(...)
       - countBy...(...)
 
example

public interface AutorRepository extends JpaRepository<Autor, Long>{
     List<Autor> findByFirstName(String firstName);
     List<Autor> findByFirstNameAndLastName(String firstName, String lastName);
     List<Autor> findByFirstNameContainingIgnoreCase(String firstName)

    // Traverse association in derived queries
    List<Autor> findByBooksTitle(String title)    
}

- Comparison Operators

    - Like
    - Containing
    - IgnoreCase
    - Between
    - LessThan or GreatherThan

- Order the result of derived query

public List<Author> findByFirstNameContainingIgnoreCaseOrderByYearAsc(String partOfFirstName);
public List<Author> findByFirstNameContainingIgnoreCaseOrderByYearDesc(String partOfFirstName);
public List<Author> findByFirstNameContainingIgnoreCaseOrderByBooksTitle(String partOfFirstName);
public List<Author> findByFirstNameContainingIgnoreCase(String partOfFirstName, Sort sort);
Sort sortYearAsc = Sort.by("year").ascending().and(Sort.by("firstName").descending());
List<Author> findByFirstNameContainingIgnoreCaseSortParameter = authorRepository.findByFirstNameContainingIgnoreCase("", sortYearAsc);

- Limiting the number of result

//Limiting
public List<Author> findFirst2ByFirstNameContainingIgnoreCaseOrderByYear(String partOfFisrtName);
- Paginate the results of a derived query

Custom Queries

JPQL - JPA Query Language is database agnostic but support subset of sql standard, not for complex queries.

Parameters

@Query("FROM Author WHERE firstName = ?1")
List<Author> findByFirstName(String firstName)
@Query("FROM Author WHERE firstName = ?1 AND lastName=?2")
List<Author> findByFirstNameAndLastName(String firstName, String lastName);

Sorting

@Query("FROM Author WHERE firstName = ?1" SORT BY lastName ASC")
List<Author> findByFirstNameOrderByLastName(String firstName);

@Query("FROM Author WHERE firstName = ?1", Sort sort)
List<Author> findByFirstName(String firstName, Sort sort);
in use
Sort mySort = Sort.by("year").ascending().and(Sort.by("firstName").descending());
List<Author> authors = autorRepository,findByFirstName("Jose Alberto", mySort);

Paginating

List<Author> findAll(Pageable pageable)

in use

// Paging
Pageable pageable = PageRequest.of(0 ,2);
Page<Author> findAllPageable = authorRepository.findAll(pageable);
log.info("findAllPageable.getTotalElements(): {}",findAllPageable.getTotalElements());
log.info(" findAllPageable.getTotalPages() : {}", findAllPageable.getTotalPages());
findAllPageable.getContent().forEach(author -> log.info("author: {}", author.getFirstName()));
log.info("Take another page ");
Pageable pageable1 = PageRequest.of(1, 2);
Page<Author> findAllPageable1 = authorRepository.findAll(pageable1);
findAllPageable1.getContent().forEach(author -> log.info("author: {}", author.getFirstName())); 

SpEL Expressions for Entity Names & Advanced Like Expressions.

@Query("FROM Author a WHERE a.firstName = ?1" )
public List<Author> findByFirstNameSpEL_param( String firstName);

@Query("FROM Author a WHERE a.firstName = :firstName" )
public List<Author> findByFirstNameSpEL_paramname( String firstName);

@Query("SELECT a FROM Author a WHERE a.year = ?#{[0]}")
public List<Author> findAuthorByYearSpEL(int year);

@Query("FROM Author WHERE UPPER(firstName) LIKE %?#{[0].toUpperCase()}%")
public List<Author> findAuthorByfirstNameLikeSpel(String firstName);

@Query("SELECT a FROM #{#entityName} a WHERE a.year > 2000")
public List<Author> findBy_entityName();

DTO Projections

Better Performance than entity projections when read-only operations.

EntityGraphs

Provides and easy and reusable way to initialize required entity associations within the query.
avoids use of n+1 querys.

Calling Stored Procedures

...

Query Projections

---

ResultSet Mapping series and How to return DTOs from Native Queries with Spring JPA

Hibernate Proxies

How and When to use getReference() method

EntityManager em = e    mf.createEntityManager();
em.getTransaction().begin();
ChessTournament chessTournament = em.getReference(ChessTourment.class, 1L);
===== No Select statement for ChessTournament is executed ======
ChessGame chessGame = new ChessGame();
chessGame.setTourment( chessTournament);
em.persist( chessGame);
em.getTransaction().commit();
==== Only insert statment gets executed ======
em.close()

Hibernate didn´t fetch the ChessTournament entity from the database, It pesist the chessGame with the reference of ChessTournament. If Using Spring Data JPA this can also be achieved by using 
T getOne(Id arg0)
T getById(Id arg0) on the repository it internally call the getReference method

Hibernate Proxies

https://www.youtube.com/watch?v=rADkGuDZkU4&t=183s&ab_channel=ThorbenJanssen

- Generated a Subclass
- Intecepts all method invocations
   - check if the proxied entity object has been initilized
     if necessary, hibernate executes a database query to initialize the entity before it calls the intercepted method on the entity object. If this happens without an active hibernate Session this causes a 
LazyInitializationException.

By default, the FetchType of All To-One associations is EAGER, that means Hibernate has to fetch the associated entity object when loading an entity. This can be changed by setting the assiciation with FetchType.LAZY, 
  Defining a LAZY loading  for a All To-One association introduces a challenge to the persistence provider. It has to find a way to get notified when your business code wants to use the assiciation and fetch it from the database. For All to-Many associations, hibernate solves this by initializing the attribute with its own collection implementations. But this doesn't work for to-One associations. The entities aren´t required to implement any interface that Hibernate could implement.
  This leaves hibernate with two options:
     It can add some code to the getter mehod, it requires bytecode enhancement. or it can generate a proxy class that´s a subclass of your entity.
The proxy object of an entity can be requested by calling the getRefference method on the entityManager or HibernateSession. This gets you a proxy object that can be used to initialize a to-One association when persisting a new or updating an existing entity.
  The getReference methos doesn´t trigger a database query, Hibenate instantiates a proxy object and only sets the primary key attribute. Hibernate delays the execution of a query until a call of getter/setter method of any non-primary key attiibute.

How to detect a proxy object.

   Most of the time a LazyInitializationException will make you aware that you´ve been working with a proxy object. Hibernate throws it if don´t have an active session and call  a getter method on any non-primary key attribute of an uninitialized proxy object.

Test
em = emf.createEntityManage();
ChessGame chessGame = em.find(ChessGame.class, this.chessGame.getId());
log.info(chassGame.getPlayerWhite().getClass().getName());
assertThat( chessGame.getPlayerWhite()).isInstanceOf(HibernateProxy.class);
assertThat( Hibernate.isInitializated(chessGame.getPlayerWhite())).isFalse();

To initialize the proxy the most common is to call a gette/setter method of a non-primary key attribute.

Use 
Hibernate.initialize(chessGame.getPlayerWhite());

But if you already know that you will use a lazily fetched association in your code, is recomended to initialize it in the same query that fetches the entity.

To get a real Entity from thier proxy
unproxiedPlayer = Hibernate.unproxy(playerWhite, ChessPlayer.class);
assertThat(unproxiedPlayer).isInstanceOf(ChessPlayer.class);

 
5 ways to initialize lazy associations and when to use them

1 .- Call a method on the mapped association. Most inefficient approach, (n+1 select issue).
        
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

Order order = em.find(Order.class, 1L);
order.getItems().size();

em.getTransaction().commit();
em.close(); 

2.- JOIN FECTH in JPQL

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

Query q=em.createQuery("SELECT o FROM Order o JOIN FETCH o.items i WHERE o.id = :id")
q.setParameter("id", 1L);
Order order = (Order) q.getSingleResult();
order.getItems().size();

em.getTransaction().commit();
em.close(); 

3.- JOIN FETCH in Criteria API

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Order> q = cb.createQuery(Order.class);
Root<Order> o = q.from(Order.class);
o.fetch("items", JoinType.INNER);
q.select(o);
q.where(cb.equal(o.get("id"), 1L));

Order order = em.createQuery(q).getSingleResult();
order.getItems().size();

em.getTransaction().commit();
em.close(); 

4.- Named Entity Graph

Named Entity Graph

JPA 2.1 Entity Graph 
– Part 1: Named entity graphs: https://goo.gl/K6sFHv JPA 2.1 Entity Graph 
– Part 2: Define lazy/eager loading at runtime: https://goo.gl/sSFbke


@Entity
@Table(name = "purchaseOrder")
@NamedEntityGraph(name = "graph-Order-items",
                  attributeNodes = @NamedAttributeNode(value= "items"))
public class Order {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "id", updatable = false, nullable = false)
  private Long id = null;
  @Version
  @Column(name = "version")
  private int version = 0;

  @Column
  private String orderNumber;

  @OneToMany(mappedBy="order", fetch = FetchType.LAZY)
  private Set<OrderItem> items = new HashSet<OrderItem>();
  
  public Long getId(){
    return this.id;
  }
   ...

  === On Test ===

  EntityManager em = emf.createEntityManager();
  em.getTransaction().begin();

  EntityGraph<?> graph = em.getEntityGraph("graph-Order-items");

  Map<String, Object> hints = new HashMap<String, Object);
  hints.put("javax.persistence.fetchGraph", graph);

  Order order = em.find(Order.class, 1L, hints);
  order.getItems().size();

  em.getTransaction().commit();
  em.close();

5.- Dynamic Entity Graph

    If you need to adapt your graph based on user input. More flexibility to and avoids entities with dozens of annotations. if you need to define a use  case specific graph, 
witch will not reuse. named EntityGraph is easier to reuse.

  EntityManager em = emf.createEntityManager();
  em.getTransaction().begin();

  EntityGraph<Order> graph = em.createEntityGraph(Order.class);
  graph.addAttributes("items");

  Map<String, Object> hints = new HashMap<String, Object);
  hints.put("javax.persistence.loadgraph", graph);

  Order order = em.find(Order.class, 1L, hints);
  order.getItems().size();

  em.getTransaction().commit();
  em.close();
    

Think twice before using CascadeType.Remove

...





eot


No hay comentarios:

Publicar un comentario