Zobrazují se příspěvky se štítkemhibernate. Zobrazit všechny příspěvky
Zobrazují se příspěvky se štítkemhibernate. Zobrazit všechny příspěvky

pondělí 25. září 2017

Sémantický identifikátor entity

Na projektech korporátních i startapových jsem se setkal vždy s tím, že si servisní třídy mezi sebou vyměnují identifikátory entit.

Často je to např. Long, pokud je datovým podkladem SQL databáze ale setkávám se i se Stringem, když je entita ze vzdáleného systému volaného přes MQ, WebServices, Rest apod.

Problém pak nastává v tom, že se tyto identifikátory často pletou.

Například

public void updateTransaction(Long transactionId, 
                              Long userId, 
                              Long operatorId, 
                              Long clientId) {
 // ... do some nasty business logic
}

Není jasné, jestli userId je identifikátor entity User - nejspíš ano. A co operatorId? Je to taky z entity User? Obzvláště, když žádná entita Operator v doménovém modelu není. 


Co kdyby rozhraní vypadalo takto
public void updateTransaction(TransactionId transactionId, 
                              UserId userId, 
                              UserId operatorId, 
                              ClientId clientId) {
 // ... do some nasty business logic
}

Takto už je sématický význam parametrů jasnější. Co ale ve skutečnosti obsahuje třída UserId?
public final class UserId {
  private final Long value;
 
  private UserId(Long value) {
    this.value = value;
  }
 
  public Long getValue() {
    return value;
  }
 
  public static UserId of(Long value) {
    if (value==null) throw new IllegalArgumentException("Parameter 'value' can't be null.");  
    return new UserId(value); 
  }
 
  public static UserId of(String value) {
    if (value==null) throw new IllegalArgumentException("Parameter 'value' can't be null.");  
    return new UserId(Long.valueOf(value)); 
  }
}


Instance je immutable a je tedy thread-safe. Zároveň hlídá, že hodnota value je ne-null-ová. Může obsahovat tolik factory metod kolik je potřeba - v tomto případě umí instanciovat z Long a ze String. 

Co když používám Hibernate? Jak to mám namapovat? Můžeme nentitu nechat tak jak je - anotovanou nad attributy - pak Hibernate mapuje hodnoty přímo na atributy bez použití set a get metod.
  
@Entity
@Table(name = "COOL_USER")
public class User {
  @Id
  @GeneratedValue
  @Column(name = "USER_ID")
  private Long id;

  @Column(name = "FIRST_NAME")
  private String firstName;
   
  @Column(name = "LAST_NAME")
  private String lastName;

  public UserId getId() {
    return UserId.of(id);
  }

  public void setId(UserId id) {
    this.id = id.getId();
   }
    
    // other set get methods for firstName and lastName

}

Čitelnost servisního kódy je mnohem lepší a navíd kompilátor za vás dělá kontrolu, jestli jste parametry nepopletli. 

Myslím si, že podobný přístup by se dal použít i na jiné jednoduché datové typy - např. AccountNumber, BirthNumber, DateOfBirth místo String, String, Date. 

středa 2. dubna 2014

Řízení transakcí přes různé DAO implementace

Na startu projektu jdemenato.cz jsme DAO vrstvu implementovali přes JPA/Hibernate - standardně dle návodů.

V produkčním režimu, kde nám neustále roste počet uživatelů, jsme s tímto naivním řešením vydrželi jen několik měsíců. Kritické dotazy, které nejvíce vytěžovaly databázi,  jsem přepsali přes Criteria API na "lepší" SQL dotazy.

Po dalších měsících produkčního života nás zákaznické požadavky přinutili některé SQL dotazy psát ručně. Nejprimitivnější jsme zrealizovali přes Hibernate native SQL ale u složitějších jsme šáhli na JdbcTemplate a později k MyBatis.

To ovšem nastolilo problém s řízením databázových transakcí, jejichž součástí je více DAO technologií.

Ukázalo se, že na podobné případy chlapci ve springu pamatovali a stačilo vyměnit HibernateTransacionManager za DataSourceTransactionManager.

Tímto způsobem je DataSource "nejmenším společním jmenovatelem" spojující DAOs implementované přes Hibernate, MyBatis i JdbcTemplate.

Aby si AnnotationSessionFactoryBean nevytvořila svůj vlastní transakční manager, je nutné nastavit ji property useTransactionAwareDataSource=true.

        
<bean name="sessionFactory" 
  class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
  <property name="dataSource" ref="dataSource"/>       
  <property name="useTransactionAwareDataSource" value="true"/>
</bean>
<bean id="sqlSessionFactory" 
         class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource"/>
  <property name="configLocation" value="classpath:/META-INF/mybatis/myBatis-configuration.xml"/>
</bean>