21 Temmuz 2023 Cuma

RegionFactory Arayüzü - Pluggable Caching Provider

Giriş
Açıklaması şöyle
org.hibernate.cache.spi.RegionFactory defines the integration between Hibernate and a pluggable caching provider. hibernate.cache.region.factory_class is used to declare the provider to use.

4 Mayıs 2023 Perşembe

@Batch Anotasyonu

Giriş
JPQL ile gelen LEFT JOIN FETCH kullanmak istemiyorsak @Batch kullanılabilir

Örnek
Şöyle yaparız.
@Entity
public class Doctor {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  private String name;

  @OneToMany(mappedBy = "doctor", fetch = FetchType.LAZY)
  @org.hibernate.annotations.BatchSize(size = 5)
  private Collection<Appointment> appointments;
}
Açıklaması şöyle. Tek bir Doctor nesnesi çeksek bile biraz daha fazla veri çekiyor. Aslında bu bence kötü bir çözüm.
Whenever Hibernate initializes one Doctor – Appointments collection it will also initialize Appointments for four more Doctors. The query which Hibernate generates is as follows.

SELECT appointmen0_.doctor_id,
 appointmen0_.id,
 appointmen0_.id, 
 appointmen0_.appointmentTime, 
 appointmen0_.doctor_id 
FROM Appointment 
 WHERE appointmen0_.doctor_id IN (?, ?, ?, ?, ?)

In our example of 10 doctors, a ceiling of N + 1 / @BatchSize, which is 10 + 1 / 5 = 3 total queries will be executed to fetch Doctors and all the Appointments. It’s not really a great optimization as still more queries are executed than we would like, but it is some improvement.

Please also bear in mind that it’s a global optimization. Once this annotation is present, referring to one of our Doctor’s appointments will result preloading Appointments for another four Doctors.

1 Aralık 2022 Perşembe

MetadataBuilderContributor Arayüzü - Custom SQL Metodu İçindir

Giriş
Açıklaması şöyle
Since Hibernate 5.2.18, you can use the MetadataBuilderContributor utility to customize the MetadataBuilder even if you are bootstrapping via JPA.
Örnek
Şöyle yaparız
import org.hibernate.boot.MetadataBuilder;
import org.hibernate.boot.spi.MetadataBuilderContributor; import org.hibernate.dialect.function.SQLFunctionTemplate; import org.hibernate.type.BooleanType; public class SQLFunctionContributor implements MetadataBuilderContributor { @Override public void contribute(MetadataBuilder metadataBuilder) { metadataBuilder.applySqlFunction("json_contains_key", new SQLFunctionTemplate(BooleanType.INSTANCE, "JSON_CONTAINS(?1, JSON_QUOTE(?2))")); } }
Açıklaması şöyle
In the above snippet, I am overriding the contribute method by providing one of the MySQL JSON functions.

The JSON_CONTAINS() function checks whether one JSON document contains another JSON document.

JSON_CONTAINS(target_json, candidate_json)

Parameters

target_jsonRequired. A JSON document.

candidate_jsonRequired. The included JSON document.

As you see, I have created a custom key “json_contains_key”. This actually will be used in Criteria API or Query DSL and will be parsed by Hibernate as JSON_CONTAINS().

And provide the custom MetadataBuilderContributor to Hibernate via the hibernate.metadata_builder_contributor configuration property.

spring.jpa.properties.hibernate.metadata_builder_contributor=com.abhicodes.customdialect.util.SQLFunctionContributor



8 Kasım 2022 Salı

PostDeleteEventListener Arayüzü

Giriş
Şu satırı dahil ederiz
import org.hibernate.event.spi.PostDeleteEventListener;
Örnek
Şöyle yaparız
public class HibernateCDCEventListener implements PostDeleteEventListener,
PostUpdateEventListener, PostInsertEventListener { public HibernateCDCEventListener(EntityManagerFactory entityManagerFactory) { SessionFactoryImplementor sessionFactory = entityManagerFactory .unwrap(SessionFactoryImplementor.class); EventListenerRegistry eventListenerRegistry = sessionFactory.getServiceRegistry() .getService(EventListenerRegistry.class); eventListenerRegistry.appendListeners(POST_DELETE, this); eventListenerRegistry.appendListeners(POST_UPDATE, this); eventListenerRegistry.appendListeners(POST_INSERT, this); } @Override public void onPostDelete(final PostDeleteEvent event) { // Handle delete events } @Override public void onPostInsert(final PostInsertEvent event) { processEntity(event.getEntity(), event.getEntity(), event.getEntity()); } @Override public void onPostUpdate(final PostUpdateEvent event) { processEntity(event.getEntity(), event.getEntity(), event.getEntity()); } @Override public boolean requiresPostCommitHanding(final EntityPersister persister) { return false; } }

7 Haziran 2022 Salı

HibernateSearch @NaturalId Anotasyonu

Giriş
Şu satırı dahil ederiz.
import org.hibernate.annotations.NaturalId;
Örnek
Şöyle yaparız
@Indexed
@Entity
@Table
@Getter
@Setter
@ToString
@EqualsAndHashCode
public class Plant {
  
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @FullTextField()
  @NaturalId
  private String name;

  @FullTextField
  @NaturalId
  private String scientificName;

  @FullTextField
  private String family;

  private Instant createdAt ;
}


18 Nisan 2022 Pazartesi

HibernateSearch @FullTextFieldAnotasyonu

Giriş
Şu satırı dahil ederiz.
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
Açıklaması şöyle
we annotate the fields we want to search on with the @FullTextField annotation. This annotation works only for string fields, but others exist for fields of different types.
Örnek
Şöyle yaparız
import org.hibernate.annotations.NaturalId;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed;


@Indexed
@Entity
@Table(name = "plant")
public class Plant {

  @FullTextField
  @NaturalId
  private String name;

  @FullTextField
  @NaturalId
  private String scientificName;

  @FullTextField
  private String family;
  
}



7 Kasım 2021 Pazar

@Length Anotasyonu - Bean Validation

Giriş
Şu satırı dahil ederiz. Bean validation için kullanılır
import org.hibernate.validator.constraints.Length;
Örnek
Şöyle yaparız
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotNull;

public record EnterpriseRecord(String id, 
  @NotNull String name, 
  @NotNull @Length(min = 2, max = 255) String address) {
}

12 Ekim 2021 Salı

StatelessSession Arayüzü - Batch İşler İçin Uygundur

Giriş
Hibernate Session arayüzü gibidir. Cache kullanmaz. Batch işler için uygundur.

beginTransaction metodu
Şöyle yaparız.
Foo foo = ...;

StatelessSession statelessSession = ...;
Transaction transaction = statelessSession.beginTransaction();

statelessSession.insert(foo);


transaction.commit(); 
getNamedQuery metodu
Örnek
Şöyle yaparız
StatelessSession session = sessionFactory.openStatelessSession();
Transaction tx = session.beginTransaction();
   
ScrollableResults articles = session.getNamedQuery("GetArticles")
    .scroll(ScrollMode.FORWARD_ONLY);
while ( articles.next() ) {
    Article article = (Article) articles(0);
    article.updateName(newArticleName);
    session.update(article);
}
   
tx.commit();
session.close();


28 Eylül 2021 Salı

HibernateSearch MassIndexer Arayüzü

Giriş
Şu satırı dahil ederiz
import org.hibernate.search.MassIndexer;
massIndexer metodu - Class
Hangi sınıfların indeksleneceğini belirtmek için kullanılır

Örnek
Şöyle yaparız
import org.hibernate.search.mapper.orm.Search;
import org.hibernate.search.mapper.orm.massindexing.MassIndexer;
import org.hibernate.search.mapper.orm.session.SearchSession;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;

@Transactional
@Component
public class Indexer {

  private EntityManager entityManager;

  private static final int THREAD_NUMBER = 4;

  public Indexer(EntityManager entityManager) {
    this.entityManager = entityManager;
  }

  public void indexPersistedData(String indexClassName) throws IndexException {

    try {
      SearchSession searchSession = Search.session(entityManager);

      Class<?> classToIndex = Class.forName(indexClassName);
      MassIndexer indexer = searchSession
        .massIndexer(classToIndex)
        .threadsToLoadObjects(THREAD_NUMBER);

        indexer.startAndWait();
    } catch (Exception e) {
      ...
  }
}
Şöyle yaparız
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public ApplicationRunner buildIndex(Indexer indexer) throws Exception {
        return (ApplicationArguments args) -> {
            indexer.indexPersistedData("com.mozen.springboothibernatesearch.model.Plant");
        };
    }
}
startAndWait metodu
Burada massIndexer() metoduna sınıf ismi geçilmiyor.

Örnek
Şöyle yaparız
@Configuration
public class HibernateSearchIndexBuild implements ApplicationListener<ApplicationReadyEvent>{

  @Autowired
  private EntityManager entityManager;

  @Override
  @Transactional
  public void onApplicationEvent(ApplicationReadyEvent event) {
    SearchSession searchSession = Search.session(entityManager);
    MassIndexer indexer = searchSession.massIndexer()
.idFetchSize(150)
.batchSizeToLoadObjects(25).threadsToLoadObjects(12);
    try {
      indexer.startAndWait();
    } catch (InterruptedException e) {
      ...
    }
  }
}

23 Eylül 2021 Perşembe

HibernateSearch Kullanımı

Maven
Açıklaması şöyle
Hibernate Search provides you with both Lucene and ElasticSearch implementations that are highly optimized for full-text search.
Örnek - Lucene
Şöyle yaparız
<dependency>
<groupId>org.Hibernate.search</groupId> <artifactId>Hibernate-search-mapper-orm</artifactId> <version>6.0.2.Final</version> </dependency> <dependency> <groupId>org.Hibernate.search</groupId> <artifactId>Hibernate-search-backend-lucene</artifactId> <version>6.0.2.Final</version> </dependency>
SpringBoot İle Kullanım
Spring için şu kütüphaneyi dahil ederiz
spring-boot-hibernate-search
Index dizinini belirtmek için şöyle yaparız
# HIBERNATE SEARCH
spring.jpa.properties.hibernate.search.backend.directory.root=/home/indexes/
yaml ile şöyle yaparız
server:
    port: 9000

spring:
    datasource:
        url: jdbc:h2:mem:mydb
        username: mozen
        password: password
    jpa:
        open-in-view: false
        properties:
            hibernate:
                search:
                    backend:
                        type: lucene
                        directory.root: ./data/index
Data Model
Entity üzerinde
@Indexed - Sınıfın indeksleneceğini belirtir
@FullTextField - Metin alanlarda arama içindir. Diğer tipte alanlar için başka anotasyonlar var
anotasyonları kullanılır

MassIndexer ile indeksleme başlatılır
SearchSession ile arama yapılır

Repository
Spring için şöyle bir kod ekleriz. Burada @NoRepositoryBean kullanılıyor, böylece bu sınıf repository olarak algılanmaz.
@NoRepositoryBean
public interface SearchRepository<T, ID extends Serializable> 
  extends JpaRepository<T, ID> {

  List<T> searchBy(String text, int limit, String... fields);
}
searchBy metodunun gerçekleştirimini ekleriz.  Spring kuralları gereği interface ismine Impl son ekini vererek yeni bir sınıf yaratırız. Burada SearchSession hibernate sınıfı. Burada fetch() metoduna limit değeri geçiliyor.  
import org.hibernate.search.mapper.orm.session.SearchSession;

@Transactional
public class SearchRepositoryImpl<T, ID extends Serializable> 
  extends SimpleJpaRepository<T, ID>
        implements SearchRepository<T, ID> {

  private final EntityManager entityManager;

  public SearchRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
    super(domainClass, entityManager);
    this.entityManager = entityManager;
  }

  public SearchRepositoryImpl(
          JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager) {
    super(entityInformation, entityManager);
    this.entityManager = entityManager;
  }

  @Override
  public List<T> searchBy(String text, int limit, String... fields) {

    SearchResult<T> result = getSearchResult(text, limit, fields);

     return result.hits();
  }

  private SearchResult<T> getSearchResult(String text, int limit, String[] fields) {
    SearchSession searchSession = Search.session(entityManager);

    SearchResult<T> result = searchSession
      .search(getDomainClass())
      .where(f -> f.match().fields(fields).matching(text).fuzzy(2))
      .fetch(limit);
    return result;
  }
}
Artık yeni bir repository şöyledir
@Repository
public interface PlantRepository extends SearchRepository<Plant, Long> {
}
Örnek - Sayfalama
Şöyle yaparız. Burada fetch() metoduna offset + limit değerleri geçiliyor. 
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageDTO<T> {

    private List<T> content;
    private long total;
}

@Override
public PageDTO<T> searchPageBy(String text, int limit, int offset, String... fields) {
  SearchResult<T> result = getSearchResult(text, limit, offset, fields);

  return new PageDTO<T>(result.hits(), result.total().hitCount());
}

private SearchResult<T> getSearchResult(String text, int limit, int offset, 
  String[] fields) {
  SearchSession searchSession = Search.session(entityManager);

  SearchResult<T> result = searchSession
    .search(getDomainClass())
    .where(f -> f.match().fields(fields).matching(text).fuzzy(2))
    .fetch(offset, limit);
    return result;
}



1 Eylül 2021 Çarşamba

SequenceStyleGenerator Sınıfı

Örnek
Şöyle yaparız
@Entity
public class Account {
  @Id
  @GenericGenerator(
    name = "account_id_generator",
    strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
    parameters = {
      @Parameter(name = "sequence_name", value = "account_id_seq"),
      @Parameter(name = "increment_size", value = "50"),
      @Parameter(name = "optimizer", value = "pooled-lo")
    })
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "account_id_generator")
    private Long id;
    ...
}

15 Haziran 2021 Salı

@Subselect Anotasyonu

Örnek
Açıklaması şöyle
Suppose, we have four tables (table1,table3,table4) each having a column “date”. I want to get list of max date for each table in a single query. 
The required output should be like

id  tablename max
1   table1         2020–03–25
2   table2         2020–04–30
3   table3         2020–02–28
4   table4         2020–03–31
Native SQL ile şöyle yaparız
select row_number() over() as id, * from (
  select ‘table1’ as tablename, max(date) from table1
  union
  select ‘table2’ as tablename, max(date) from table2 
  union
  select ‘table3’ as tablename, max(date) from table3
  union
  select ‘table4’ as tablename, max(date) from table4
  order by tablename
) t1;
Hibernate ile şöyle yaparız
import java.time.LocalDate;
import javax.annotation.concurrent.Immutable;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.annotations.Subselect;
import org.hibernate.annotations.Synchronize;

@Entity
@Subselect( “select row_number() over() as id, * from (" +
"select ‘table1’ as tablename, max(date) from table1 “ +
  “union " +
"select ‘table2’ as tablename, max(date) from table2 " +
"union " +
"select ‘table3’ as tablename, max(date) from table3 ” +
  “union " +
"select ‘table4’ as tablename, max(date) from table4 order by tablename) t1”)
@Synchronize({“table1”, “table2”,”table3",”table4"})
@Immutable
public class TableStatus {
 
  @Id
  private Long id;
  private String tablename;
  private LocalDate max;
  
}

@Repository
public interface TableStatusRepository extends JpaRepository<TableStatus,Long>{}
Açıklaması şöyle
Hibernate doesn’t know which database tables are used by the SQL statement configured in the @Subselect annotation. You can provide this information by annotating the entity with @Synchronize. That enables Hibernate to flush pending state transitions on tables table1, table2,table3 and table4 entities before selecting a TableStatus entity.

16 Ocak 2020 Perşembe

@Formula Anotasyonu

Giriş
Entity yüklenirken başka tablodan veri çekip yüklemek için kullanılabilir. Sınıf içindeki başka bir alana erişebilir. Açıklaması şöyle
Even if it is best to adhere to the JPA standard, in reality, many JPA providers offer additional features targeting a high-performance data access layer requirements.
For this purpose, Hibernate comes with the following non-JPA compliant features:
Örnek
Şöyle yaparız.
@Formula("(select current_date())")
Date currentDate;
Örnek
Şöyle yaparız
@Entity
@Table(name = "roles")
public class Role {

  @Id
  @GeneratedValue(generator = "roles_id_seq", strategy = GenerationType.SEQUENCE)
  @SequenceGenerator(name = "roles_id_seq", sequenceName = "roles_id_seq",
    allocationSize = 1)
  @Column(name = "id")
  private Long id;
  ...

  @Formula("(SELECT COUNT(*) FROM user_roles us WHERE us.id_role = id)")
  private Integer countUsing;
}
Örnek
Şöyle yaparız.
@MappedSuperclass
public abstract class BaseEntity {

  @Id
  protected String id;

  @Formula("(select d from SOME_TABLE ST ST.some_id = id)")
  private Long property4;

}