Hibernate - Jak ułatwić programowanie baz danych w języku JAVA

Autor: Marcin Kasiński
08.12.2012 21:41:49 +0200

Pisząc aplikacje JAVA komunikujące się z bazą danych zaczynamy od komunikowania się za pomocą API JDBC. Jest to uniwersalne API pozwalające na wykonywanie dowolnych operacji SQL na bazie. Nie jest tu zbytnio istotne z jaką bazą się komunikujemy. za samą natywną komunikacje z bazą odpowiada sterownik odpowiedni dla danego silnika bazy. Rolą programisty jest tutaj napisanie odpowiednich operacji SQL SELECT, INSERT, UPDATE DELETE itp. Czasami developer zna podstawy języka SQL i nie chce zbytnio się skupiać na programowaniu zapytań SQL do bazy. Czasami mimo nawet dużych kompetencji programisty w zakresie SQL chce się on skupić tylko i wyłącznie na programowaniu logiki JAVA. Tutaj rozwiązaniem może być Hibernate , biblioteka przysłaniająca operacje SQL.

Wstęp

W poniższym artykule postaram się opisać kilka ważniejszych zagadnień związanych z tą biblioteką wraz z odpowiednim udokumentowaniem tego przykładami. Wykorzystując Hibernate programista przygotowuje odpowiednie klasy encji odpowiadające tabelom w bazie, klasy dostępu do danych , klasy serwisu realizujące odpowiednią logikę na bazie no i oczywiście kod klienta.

Przygotowanie encji

W naszej przykładowej aplikacji zakładam poniższe encje, reprezentujące tabele w bazie (w naszym przykładzie będzie to baza MySQL):

  • użytkownik
  • departament
  • adres
  • kursy, w którym użytkownik uczestniczył

Tworzenie tabel MySQL

Poniżej operacje SQL tworzące obiekty w bazie danych MySQL Tabela departments zawierająca słownik departamentów


CREATE TABLE `departments` (
    `department_id` BIGINT(20) NOT NULL AUTO_INCREMENT,
    `dept_name` VARCHAR(50) NOT NULL DEFAULT '0',
    PRIMARY KEY (`department_id`),
      UNIQUE KEY `Unique_dept_name` (`dept_name`)
);

Tabela addresses zawierająca adresy użytkowników


CREATE TABLE IF NOT EXISTS `addresses` (
  `address_id` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `street` varchar(50) NOT NULL,
  `street_number` varchar(50) NOT NULL,
  `flat_number` varchar(50) NOT NULL,
  PRIMARY KEY (`address_id`),
  UNIQUE KEY `street` (`street`)  
) ENGINE=InnoDB  DEFAULT CHARSET=latin2 ;

Tabela users zawierająca rekordy samych użytkowników


CREATE TABLE IF NOT EXISTS `users` (
  `userid` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL,
  `department_id` BIGINT(20) NULL DEFAULT NULL,
  `manager_id` BIGINT(20) NULL DEFAULT NULL,
  `address_id` BIGINT(20) NULL DEFAULT NULL,
  PRIMARY KEY (`userid`),
  UNIQUE KEY `username` (`username`),
  INDEX `FK_DEPT` (`department_id`),
    CONSTRAINT `FK_DEPT` FOREIGN KEY (`department_id`) REFERENCES `departments` (`department_id`),
  INDEX `FK_ADDR` (`address_id`),
    CONSTRAINT `FK_ADDR` FOREIGN KEY (`address_id`) REFERENCES `addresses` (`address_id`),
    CONSTRAINT `FK_MANAGER` FOREIGN KEY (`manager_id`) REFERENCES `users` (`userid`)
  
) ENGINE=InnoDB  DEFAULT CHARSET=latin2 ;

Tabela courses zawierająca słownik kursów


CREATE TABLE `courses` (
    `course_id` BIGINT(20) NOT NULL AUTO_INCREMENT,
    `course_name` VARCHAR(50) NOT NULL DEFAULT '0',
    PRIMARY KEY (`course_id`),
      UNIQUE KEY `Unique_course_name` (`course_name`)
);

Tabela user_course zawierająca złączenie użytkownika z kursem


CREATE TABLE `user_course` (
    `user_id` BIGINT(20) NOT NULL,
    `course_id` BIGINT(20) NOT NULL,
    PRIMARY KEY (`user_id`, `course_id`),
    INDEX `FK_COURSE` (`course_id`),
    CONSTRAINT `FK_USER` FOREIGN KEY (`user_id`) REFERENCES `users` (`userid`),
    CONSTRAINT `FK_COURSE` FOREIGN KEY (`course_id`) REFERENCES `courses` (`course_id`)
);

Tworzenie odpowiednich encji JAVA

Dla każdej tabeli, jeśli chcemy wykonywać na niej operacje, musimy przygotować odpowiednią klasę opisującą encję. Wszelkie oznaczenia klasy dla biblioteki hibernate możemy wykonać w odpowiednim pliku konfiguracyjnym lub , co my zrobimy , poprzez wprowadzenie w klasie odpowiednich adnotacji.

Klasa naszej encji musi implementować interfejs Serializable. Opisujemy ją adnotacjami @Entity , określającą, ze klasa jest encją bazy oraz @Table określającą nazwę tabeli z jaką związana będzie nasza encja.


@Entity
@Table(name="users")
public class User implements Serializable {

Każda prosta kolumna w tabeli określana jest w klasie , jako zwykłe pole private odpowiedniego typu. Jeśli pole nie ma żadnej adnotacji oznacza to, że odpowiadająca polu kolumna w bazie ma taką samą nazwę , jak pole. Dodając adnotacje @Column możemy określić nazwę kolumny oraz dodatkowe parametry.

	@Column(name="username", unique=true)
	private String username;

Dwie kwestie, jakie tu dodatkowo chciałbym omówić, to sytuacja , kiedy nie chcemy aby konkretne pole z naszej klasy encji było mapowane na kolumnę oraz adnotacja dla unikalnego klucza głównego w tabeli. Jeśli nie chcemy, aby konkretne pole z naszej klasy encji było mapowane na kolumnę, bo dla przykładu w naszej encji będzie to pole wyliczalna na podstawie innych kolumn pole takie w klasie opisujemy adnotacją @Transient

	@Transient
	private Long suma_x;

W przypadku unikalnego klucza głównego w tabeli pole głównego klucza id typu AUTOINCREMENT w klasie możemy opisać następująco:

	@Column(name="userid", unique=true)
	@Id
	@GeneratedValue(strategy=GenerationType.AUTO)
	private Long userId;

Złączenia encji

W przypadku naszych tabel określamy w nich złączenia pomiędzy rekordami tabel, tj. np. złączenie pomiędzy użytkownikiem , a adresem, czy też użytkownikiem, a kursem w jakim uczestniczył.

W przypadku prostego złączenia typu jeden do jednego pole w klasie oznaczamy adnotacją @OneToOne oraz @JoinColumn gdzie określamy kolumnę złączeniową. W naszym przypadku proste złączenie dotyczy departamentu, adresu oraz menadżera użytkownika. W przypadku departamentu i adresu złączenie jest do odpowiadających innych tabel. W przypadku menadżera złączenie następuje z tą samą tabelą users, gdzie kolumna manager_id wskazuje na identyfikator rekordu określającego przełożonego danego użytkownika.

	@OneToOne
	@JoinColumn(name="department_id")
    private Department department;
	...
	@OneToOne
    @JoinColumn(name="manager_id")
    private User manager;
	...
	@OneToOne
    @JoinColumn(name="address_id")
    private Address address;

Tak jak w przypadku tabel istnieją złączenia typu OneToMany, ManyToOne oraz ManyToMany tak samo tego typu złączenia możemy opisać w naszych encjach poprzez odpowiednie adnotacje.

W naszym przypadku adnotacją OneToMany oznaczamy pole subordinates , określające pracowników, których menadżerem jest dany użytkownik. W adnotacji określamy pole w klasie opisujące klucz obcy w relacji (w naszym przypadku pole manager). Oznacza to, że w naszej klasie mamy dwustronną relację, pole manager określa przełożonego użytkownika, a subordinates listę pracowników, których menadżerem jest dany użytkownik.


	@OneToMany(mappedBy="manager", fetch=FetchType.LAZY)
	private Set<User> subordinates = new HashSet<User>();

Bardziej skomplikowaną relacją jest ManyToMany. W naszym przypadku będzie to relacja dotycząca kursów odbytych przez danego użytkownika. Relację taką opisujemy poprzez definicje tabeli złączeniowej, oraz kolumn w tej tabeli opisujących identyfikatory jednej i drugiej tabeli, jakie łączymy. W naszym przypadku @JoinTable(name="user_course" opisuje tabelę złączeniową, joinColumns={@JoinColumn(name="user_ID")} określa kolumnę łączącą tabele users , a inverseJoinColumns={@JoinColumn(name="course_ID")} określa kolumnę łączącą tabele courses.


	@ManyToMany(fetch=FetchType.LAZY, cascade = {CascadeType.ALL})
    @JoinTable(name="user_course",
                joinColumns={@JoinColumn(name="user_ID")},
                inverseJoinColumns={@JoinColumn(name="course_ID")})

	private Set<Course> courses = new HashSet<Course>();

Analogicznie opisane są pozostałe klasy naszego przykładu, czyli Address, Course, oraz Department. Nie będę tu umieszczał przykłady adnotacji z tych klas, aby się nie powtarzać.

Pobieranie i kaskadowość

Z powyższych adnotacji należałoby omówić dodatkowe parametry złączenia, fetch oraz cascade . Parametr fetch określa w którym momencie dane z zależnych tabel opisanych w złączeniu są odczytywane z bazy. Wartość FetchType.LAZY oznacza, że dane zostaną odczytane z bazy dopiero w momencie , kiedy nastąpi odwołanie do obiektu. Wartość FetchType.EAGER oznacza, że dane zostaną odczytane z bazy natychmiast jak tylko zainicjowany zostanie obiekt nadrzędny. Aby poprzeć to konkretnym przykładem proszę przeanalizować adnotacje @ManyToMany(fetch=FetchType.LAZY, cascade = {CascadeType.ALL}) znajdującą się przy odwołaniu do kursów użytkownika. Oznaczyłem sposób pobierania na LAZY ponieważ wiedziałem, że w większości przypadków te dane nie będą mnie interesowały, więc ze względów wydajnościowych nie ma sensu aby trzymać je w pamięci. Zostaną one wczytane tylko i wyłącznie, kiedy w kodzie odwołam się do tych kursów. Parametr ten więc jak widać pozwalać optymalizować aplikacje pod względem wydajności.

Termin kaskadowość w encjach Hibernate odpowiada kaskadowości bazodanowej. Oznacza on określenie zachowania co do obiektów zależnych w przypadku operacji modyfikujących główny obiekt. W naszym przypadku np poprzez kaskadowość możemy określić co robimy z kursami użytkownika w momencie jego usunięcia. Przy takich zależnościach możemy np. określić ,że w takim przypadku też obiekty zależne usuwamy. To samo się tyczy tworzenia obiektów. W przypadku tworzenia obiektu głównego możemy określić które jego obiekty zależne automatycznie zostaną również utworzone. W naszym przykładowym kodzie w momencie zapisu do bazy użytkownika , poprzez adnotacje @ManyToMany(fetch=FetchType.LAZY, cascade = {CascadeType.ALL}) przy kursach określiliśmy, że zostaną one automatycznie zapisane do bazy.

Klasy dostępu do bazy

Następnym krokiem jest przygotowanie odpowiednich klas realizujących dostęp do bazy , celem odwzorowania zmian w klasach JAVA na operacje bazodanowe.

Pierwszym krokiem jest utworzenie interfejsu, w którym umieścimy metody nas interesujące realizujące operacje bazodanowe. W naszym wypadku będą metody pozwalające na zapis obiektów do bazy ( save oraz save2objects ), zmianę danych w bazie ( update ), usunięcie obiektu z bazy danych ( delete ), oraz metody pozwalające na pobranie danych z bazy na podstawie różnych kryteriów wyszukiwania ( findByUserId , findByUserName , findSubordinatesByManagerId ).


public interface UserDao {
	
	void save(User user);
	void save2objects(User user,Department department);
	
	void update(User user);
	
	void delete(User user);
	
	User findByUserId(Long userId);
	User findByUserName(String userName);
	List<User> findSubordinatesByManagerId(Long managerId);

}

Po przygotowaniu interfejsu możemy stworzyć klasę implementującą komunikację z baza dla każdej metody opisanej w interfejsie. Klasa taka musi rozszerzać klasę HibernateDaoSupport oraz implementować nasz interfejs UserDao . Proste operacje takie jak usunięcie obiektu, jego modyfikacja i załadowanie poprzez id dokonuje się poprzez pobranie obiektu getHibernateTemplate i na nim wykonanie odpowiednich metod update , delete , load oraz save .

Trochę bardziej skomplikowane jest pobieranie obiektu, bądź obiektów na podstawie określonych warunków. Przykładem tego są metody findByUserName oraz findSubordinatesByManagerId . W metodach tym wykorzystujemy specjalną klasę Criteria określającą warunki naszego zapytania. Warunki te z kolei możemy dodawać za pomocą metod klasy Restrictions . W naszym przypadku użyliśmy metody eq oznaczającej prosty operator równości. Argumenty tej metody to pole klasy jakie porównujemy oraz porównywana wartość. Inne operatory mają odpowiednie swoje odpowiedniki metod w klasie Restrictions .

package mkpackage.dao;

import java.util.List;
import java.util.Set;

import mkpackage.model.Department;
import mkpackage.model.User;

import org.hibernate.Criteria;
import org.hibernate.Transaction;
import org.hibernate.classic.Session;
import org.hibernate.criterion.Restrictions;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

public class UserDaoImpl extends HibernateDaoSupport implements UserDao {

	public void update(User user) {
		getHibernateTemplate().update(user);
	}

	public void delete(User user) {
		getHibernateTemplate().delete(user);
	}

	public User findByUserId(Long userId) {

		return (User) getHibernateTemplate().load(User.class, userId);

	}

	public User findByUserName(String userName) {

		return (User) getSession().createCriteria(User.class)
				.add(Restrictions.eq("username", userName))
				.uniqueResult();

	}

	public void save2objects(User user, Department department) {
		getHibernateTemplate().save(department);
		getHibernateTemplate().save(user);

	}

	public void save(User user) {
		getHibernateTemplate().save(user);

	}

	@Override
	public List<User> findSubordinatesByManagerId(Long managerId) {

		List list = getSession().createCriteria(User.class)
				.add(Restrictions.eq("manager.userId", managerId))
				.list();

		return list;

	}

}

Klasy dostępu do usług

Klasy dostępu do usług to warstwa udostępniająca konkretne usługi biznesowe dla aplikacji. W naszym przypadku metody serwisu odpowiadają dokładnie metodom klasy dostępu do danych. W moim przykładzie tak jest dla uproszczenia. W rzeczywistości nie zdarza tak się często. Klasa dostępu do bazy zawiera metody atomowe. Klasa serwisu zawiera metody biznesowe , w których wywołujemy metody atomowe klasy dostępu do danych.

W przypadku serwisu analogicznie jak to miało miejsce w przypadku klasy dostępu do danych zaczynamy tu również od przygotowania interfejsu z listą metod serwisu.

package mkpackage.service;

import java.util.List;
import mkpackage.model.Department;
import mkpackage.model.User;

public interface UserServiceInterface {

	void save(User user);

	void save2objects(User user, Department department);

	void update(User user);

	void delete(User user);

	List<User> findSubordinatesByManagerId(Long managerId);

	User findByUserId(Long userId);

	User findByUserName(String userName);

}

Następnie implementujemy klasę serwisu. W klasie tej tworzymy pole typu interfejsu dostępu do danych. W naszym przypadku UserDao . Klasę tą wykorzystujemy w naszych metodach biznesowych serwisu celem wywołania atomowych metod naszej klasy dostępu do danych.

package mkpackage.service;

import java.util.List;
import org.springframework.transaction.annotation.Transactional;

import mkpackage.dao.UserDao;
import mkpackage.model.Department;
import mkpackage.model.User;

public class UserServiceImpl implements UserServiceInterface {

	UserDao userDao;

	public UserDao getUserDao() {
		return userDao;
	}

	public void setUserDao(UserDao userDao) {
		this.userDao = userDao;
	}

	@Transactional
	public void save2objects(User user, Department department) {
		userDao.save2objects(user, department);
	}

	@Transactional
	public void save(User user) {
		userDao.save(user);
	}

	public void update(User user) {
		userDao.update(user);
	}

	public void delete(User user) {
		userDao.delete(user);
	}

	public List<User> findSubordinatesByManagerId(Long managerId) {
		return userDao.findSubordinatesByManagerId(managerId);
	}

	public User findByUserId(Long userId) {
		return userDao.findByUserId(userId);
	}

	public User findByUserName(String userName) {
		return userDao.findByUserName(userName);
	}

}

Klient Hibernate

Ostatnią rzeczą, jaka nam pozostała jest przedstawienie jak wywoływać w kodzie metody biznesowe serwisu. Do tego celu posłuży nam biblioteka Spring , za pomocą której opiszemy nasze klasy hibernate. Bardzo szczegółowo nie będę opisywał tu wszystkie parametry. Skupie się bardziej na posłużeniu się przykładem. Pierwszym elementem jest plik applicationContext.xml . W pliku tym opisujemy obiekty związane z połączeniem do bazy danych, hibernateTemplate , sessionFactory , txManager oraz dataSource . Poza tymi obiektami dodatkowo opisuje on naszą klasę implementującą dostęp do danych oraz serwis( service oraz userDao ). Parametry opisujące połączenie do bazy danych oraz parametry biblioteki Hibernate wczytywane są tu z zewnętrznych plików jdbc.properties oraz hibernate.properties .

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util"
	xmlns:tx="http://www.springframework.org/schema/tx"

	xsi:schemaLocation="
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
		http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

	<context:property-placeholder location="file:in/jdbc.properties" />

	<context:component-scan base-package="mkpackage.dao" />


	<bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
		<property name="sessionFactory" ref="sessionFactory" />
	</bean>

	<!-- Hibernate session factory -->
	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />

		<property name="annotatedClasses">
			<list>
				<value>mkpackage.model.User</value>
				<value>mkpackage.model.Department</value>
				<value>mkpackage.model.Address</value>
				<value>mkpackage.model.Course</value>
			</list>
		</property>
		<property name="hibernateProperties">
			<util:properties location="file:in/hibernate.properties" />
		</property>
		<property name="eventListeners">
			<map>
				<entry key="merge">
					<bean
						class="org.springframework.orm.hibernate3.support.IdTransferringMergeEventListener" />
				</entry>
			</map>
		</property>
	</bean>

	<!-- enable the configuration of transactional behavior based on annotations -->


	<tx:annotation-driven transaction-manager="txManager" />


	<bean id="txManager"
		class="org.springframework.orm.hibernate3.HibernateTransactionManager">
		<property name="sessionFactory" ref="sessionFactory" />

	</bean>


	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="${jdbc.driver}" />
		<property name="url" value="${jdbc.url}" />
		<property name="username" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
	</bean>


	<bean id="service" class="mkpackage.service.UserServiceImpl">
		<property name="userDao" ref="userDao" />
	</bean>

	<bean id="userDao" class="mkpackage.dao.UserDaoImpl">
		<property name="sessionFactory" ref="sessionFactory"></property>
	</bean>


</beans>

W pliku hibernate.properties opisujemy parametry biblioteki hibernate.


hibernate.dialect = org.hibernate.dialect.MySQLDialect
hibernate.use_sql_comments = true
hibernate.show_sql = false
hibernate.format_sql = false
hibernate.query.substitutions = true 1, false 0
hibernate.connection.release_mode = after_transaction
hibernate.jdbc.use_streams_for_binary = true
hibernate.jdbc.batch_size = 20
hibernate.generate_statistics = true
hibernate.connection.autocommit=false
#ponizsze dla wlasnych transakcji
hibernate.current_session_context_class=thread
#hibernate.connection.useUnicode=true
hibernate.connection.characterEncoding=windows-1250
hibernate.connection.charSet=windows-1250

W pliku jdbc.properties opisujemy parametry połączenia do bazy.

jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/marcin?characterEncoding=cp1250&characterSetResults=cp1250
jdbc.username = root
jdbc.password = hasło

Ostatnim elementem jest kody wywołujący nasze operacje serwisu. W pierwszym kroku za pomocą klasy ClassPathXmlApplicationContext wczytujemy naszą konfiguracje. Następnie z konfiguracji pobieramy interfejs UserServiceInterface naszego serwisu poprzez metodę getBean Mając serwis możemy swobodnie wywoływać w nim nasze metody biznesowe.

package mkpackage;

import java.util.List;
import mkpackage.model.Address;
import mkpackage.model.Course;
import mkpackage.model.Department;
import mkpackage.model.User;

import mkpackage.service.UserServiceInterface;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class HibernateClient {

	public static void main(String[] args) {
		HibernateClient client = new HibernateClient();
		System.out.println("Start");
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
				"file:in/applicationContext.xml");

		
		
		UserServiceInterface service = (UserServiceInterface) ctx
				.getBean("service");

				//.... Tu operacje wykonywanie na obiekcie service
				
		System.out.println("End");
	
	}

}

Wywoływanie własnych zapyań SQL

Czasami istnieje potrzeba, w klasie dostępu do danych, wywołania natywnego zapytania SQL. Możemy tego dokonać poprzez wprowadzenie do metody klasy kodu jak poniżej.

	String query="INSERT INTO ...";

		Query queryObj = getSession().createSQLQuery(query);
		
		int updated = queryObj.executeUpdate();

Transakcyjność

W naszej przykładowej aplikacji transakcje zatwierdzane są automatycznie zaraz po wywołaniu kodu. Oznacza to, że jeśli w naszym serwisie, bądź w naszej klasie dostępu do danych mamy kod, który wywołuje wiele metod zmieniających dane w bazie, pojawienie się wyjątku nie powoduje automatycznie wycofania wcześniejszych zmian w bazie. Jeśli jest taka potrzeba aby metoda serwisu wywoływana była w jednej transakcji musimy taką metodę implementacji naszego serwisu opisać adnotacją @Transactional .

Cache pierwszego i drugiego poziomu

Temat cache w Hibernate jest dość rozbudowanym tematem. Zazwyczaj jest on opisywany w oddzielnych artykułach. Mimo to postaram się go po krótce opisać. Cache w Hibernate, jak w każdym innym rozwiązaniu oznacza zwiększenie wydajności poprzez ograniczenie ilości zapytań do bazy. Odbywa się to poprzez przechowywanie w pamięci podręcznej lub w innym miejscy danych o jakie już pytaliśmy na wypadek gdyby pytanie pojawiło się ponownie.

Pierwszą rzeczą, jaką musimy zrobić jest dodanie w pliku konfiguracyjnym hibernate.properties parametrów włączających cache.


#second level cache
hibernate.cache.provider_class = org.hibernate.cache.EhCacheProvider
hibernate.cache.use_query_cache = true
hibernate.max_fetch_depth = 4
hibernate.cache.use_second_level_cache = true
hibernate.cache.region.factory_class = net.sf.ehcache.hibernate.EhCacheRegionFactory
net.sf.ehcache.configurationResourceName = ehcache.xml

W parametrach tych wskazujemy na implementacje cache, jaka ma być używana w naszym rozwiązaniu. My użyjemy ehcache .

Opis konfiguracji naszego cache znajduje się w pliku ehcache.xml określonym parametrem net.sf.ehcache.configurationResourceName

<ehcache>
	<diskStore path="java.io.tmpdir" />
	<defaultCache maxElementsInMemory="2000" eternal="false"
		timeToIdleSeconds="30" timeToLiveSeconds="30" overflowToDisk="true"
		diskPersistent="false" diskExpiryThreadIntervalSeconds="1000"
		memoryStoreEvictionPolicy="LRU" />


	<cacheManagerEventListenerFactory
		class="mkpackage.MKCacheListenerFactory" properties="" />
</ehcache>

Następnie możemy przejść do modyfikacji naszych klas. Pierwszą rzeczą, jaką musimy zrobić jest oznaczenie naszej klasy adnotacją @Cache wraz z określeniem trybu cache.

@Cache(region="users", usage = CacheConcurrencyStrategy.READ_ONLY)

Dalej w klasie implementacji dostępu do danych przy używaniu klasy Criteria używamy metody setCacheable(true) .

Przykład:



	List list = getSession().createCriteria(User.class)
				.add(Restrictions.eq("manager.userId", managerId))
				.setCacheable(true).list();

powrót
Zachęcam do przedstawienia swoich uwag i opinii w polu komentarzy.

Komentarze

SQL vs. Hibernate

Czasami istnieje potrzeba, w klasie dostępu do danych, wywołania natywnego zapytania SQL To znaczy "potrzeba" czy "koniecznosc"? Czy da sie calkowiecie uniknac stosowanie SQLa czy nie? Bo jezeli nie to niewarta skorka za wyprawke.

zbig@op.pl 2019-01-28 14:53:22

2017-06-21 22:49:28

Dzięki

Bardzo pomogło. Ktoś się czepiał końcówek - w tekstach technicznych komunikacyjność języka ma pierwszeństwo przed poprawnością formalna.

bogdanjany@yahoo.com 2015-06-12 14:41:17

Czwórka z plusem

Sugeruję przejrzeć treść pod kątem dokładności językowej. Wiem że 95% developerów nie widzi takich rzeczy, ale literówki, błędne odmiany, niedokładność stylistyczna - nie powinno to mieć miejsca. Jest to - obok merytorycznej poprawności i spoistości - wizytówka autora. Technicznie artykuł bardzo mi odpowiada. Idea zarysowana poprawnie, detale zaś można doczytać w sieci. Dziękuję i pozdrawiam!

sibellius@prokonto.pl 2015-04-13 08:59:59

Dzięki

Super artykuł. Udało ci się opisać Hibernate i Sring w jednym.

Marek 2015-02-11 23:13:58

Powiem krótko - atrykuł świetny, same konkrety bez owijania w bawełnę. Brawo dla autora :) PS. Jedynie co można by było w nim zmienić to kolor tekstu (jasny popielaty za bardzo zlewa się z tłem) i jego wielkość...

Wicia 2014-05-05 09:14:43

Dodaj Komentarz