[Java] Spring, Hibernate 3 i Apache Derby z Maven w Eclipse – kompletny przykład

Na początek musimy wygenerować nową aplikację. Tutaj z pomocą przychodzi mechanizm archetypów (a ściślej mówiąc Archetype Plugin). Poprzez wywołanie poniższego polecenia stworzymy kompletną strukturę prostego projektu wraz z podstawowym plikiem pom.xm

mvn archetype:generate -DgroupId=hibernate_spring.example -DartifactId=hibernate-spring-derby-app -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

Zanim dokonamy importu projektu do Eclipse proponuję wykonać dodatkowy krok i utworzyć w wygenerowanym projekcie katalog src/main/resources.

W celu zapewnienia sobie dalszej komfortowej pracy instalujemy plugin m2e dostępny w Eclipse Marketplace

Następnie importujemy projekt do Eclipse (File→Import…)

Import istniejącego projektu Maven do Eclipse z m2e

i edytujemy plik pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>hibernate-spring.example</groupId>
	<artifactId>hibernate_spring-derby-app</artifactId>
	<packaging>jar</packaging>
	<version>1.0-SNAPSHOT</version>
	<name>hibernate-spring-derby-app</name>
	<url>http://maven.apache.org</url>
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<!-- Shared version number properties -->
		<org.springframework.version>3.1.1.RELEASE</org.springframework.version>
		<org.slf4j.version>1.6.1</org.slf4j.version>
		<org.apache.derby.version>10.8.1.2</org.apache.derby.version>
		<org.hibernate.version>3.6.7.Final</org.hibernate.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.8.1</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>${org.hibernate.version}</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.6.1</version>
		</dependency>

		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.16</version>
		</dependency>
		<dependency>
			<groupId>javassist</groupId>
			<artifactId>javassist</artifactId>
			<version>3.12.0.GA</version>
		</dependency>
		<dependency>
			<groupId>org.apache.derby</groupId>
			<artifactId>derby</artifactId>
			<version>${org.apache.derby.version}</version>
		</dependency>
		<dependency>
			<groupId>org.apache.derby</groupId>
			<artifactId>derbytools</artifactId>
			<version>${org.apache.derby.version}</version>
		</dependency>

		<!-- Core utilities used by other modules. Define this if you use Spring 
			Utility APIs (org.springframework.core.*/org.springframework.util.*) -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${org.springframework.version}</version>
		</dependency>

		<!-- Application Context (depends on spring-core, spring-expression, spring-aop, 
			spring-beans) This is the central artifact for Spring's Dependency Injection 
			Container and is generally always defined -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${org.springframework.version}</version>
			<exclusions>
				<exclusion>
					<artifactId>commons-logging</artifactId>
					<groupId>commons-logging</groupId>
				</exclusion>
			</exclusions>
		</dependency>
		<!-- Support for testing Spring applications with tools such as JUnit and 
			TestNG This artifact is generally always defined with a 'test' scope for 
			the integration testing framework and unit testing stubs -->

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>${org.springframework.version}</version>
			<scope>test</scope>
		</dependency>

		<!-- JDBC Data Access Library (depends on spring-core, spring-beans, spring-context, 
			spring-tx) Define this if you use Spring's JdbcTemplate API (org.springframework.jdbc.*) -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>${org.springframework.version}</version>
		</dependency>
		<!-- Object-to-Relation-Mapping (ORM) integration with Hibernate, JPA, 
			and iBatis. (depends on spring-core, spring-beans, spring-context, spring-tx) 
			Define this if you need ORM (org.springframework.orm.*) -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>${org.springframework.version}</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<compilerVersion>1.7</compilerVersion>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

Następnie w pliku src/main/resourcs/beans.xml dodajemy konfigurację

<?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:lang="http://www.springframework.org/schema/lang"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-3.0.xsd">
	<!-- DataSource Property -->
	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName">
			<value>org.apache.derby.jdbc.EmbeddedDriver</value>
		</property>
		<property name="url">
			<!-- Tryb "w pamięci". Po usunięciu podprotokołu 'memory' baza będzie 
			wykorzystywała domyślny tryb dyskowy -->
			<value>jdbc:derby:memory:db/db.derby;create=true</value>
		</property>
	</bean>

	<bean id="hibernateProperties"
		class="org.springframework.beans.factory.config.PropertiesFactoryBean">
		<property name="properties">
			<props>
				<prop key="hibernate.dialect">org.hibernate.dialect.DerbyDialect</prop>
				<prop key="hibernate.show_sql">true</prop>
				<prop key="hibernate.cache.provider_class">org.hibernate.cache.NoCacheProvider</prop>
				<!-- 
				https://jira.springsource.org/browse/SPR-4207
				<prop key="hibernate.current_session_context_class">thread</prop>
				 -->
				<prop key="hibernate.hbm2ddl.auto">create-drop</prop>
			</props>
		</property>
	</bean>

	<!-- Hibernate SessionFactory -->
	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
		<property name="dataSource">
			<ref local="dataSource" />
		</property>
		<property name="hibernateProperties">
			<ref bean="hibernateProperties" />
		</property>
		<!--
		<property name="exposeTransactionAwareSessionFactory">
			<value>false</value>
		</property>
		 -->
	</bean>
	<bean id="transactionManager"
		class="org.springframework.orm.hibernate3.HibernateTransactionManager">
		<property name="sessionFactory">
			<ref local="sessionFactory" />
		</property>
	</bean>
</beans>

W celu sprawdzenia, czy jak dotąd wszystko działa napiszemy test jednostkowy.

package hibernate_spring.example;

import org.hibernate.SessionFactory;
import org.hibernate.classic.Session;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/beans.xml" })
public class AppTest {

	@Autowired
	SessionFactory sessionFactory;

	@Test
	@Transactional
	public void testSetup() {
		Session session = sessionFactory.getCurrentSession();
	}
}

W tym momencie workspace’a Eclipse wygląda mniej więcej tak

Wynik uruchomienia testu jednostkowego w Eclipse

Czas na model domeny! Ten jest bardzo prosty, bo składa się z zaledwie dwóch bytów Student i Przedmiot połączonych relacją wiele-do-wielu (Baz danych 101). Pliki mapowań wyglądają następująco

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
	"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
	"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
	<class name="hibernate_spring.example.Course" table="COURSE">
		<meta attribute="class-description">
			Informacje o przedmiocie
		</meta>
		<id name="courseId" type="long" column="COURSE_ID">
			<generator class="native" />
		</id>
		<property name="courseName" type="string" column="COURSE_NAME" />
		<set name="students" inverse="true" table="STUDENT_COURSE">
			<key column="COURSE_ID" />
			<many-to-many column="STUDENT_ID" class="hibernate_spring.example.Student" />
		</set>

	</class>
</hibernate-mapping>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
	<class name="hibernate_spring.example.Student" table="STUDENT">
		<meta attribute="class-description">Informacje o studencie</meta>
		<id name="studentId" type="long" column="STUDENT_ID">
			<generator class="native" />
		</id>
		<property name="studentName" type="string" length="100"
			not-null="true" column="STUDENT_NAME" />
		<set name="courses" table="STUDENT_COURSE" cascade="all">
			<key column="STUDENT_ID" />
			<many-to-many column="COURSE_ID" class="hibernate_spring.example.Course" />
		</set>
	</class>
</hibernate-mapping>

Klasy POJO też są niezwykle proste

package hibernate_spring.example;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

public class Student implements Serializable {

	private Long studentId;
	private String studentName;
	private Set<Course> courses = new HashSet<Course>(0);

	public Student() {

	}

	public Student(String studentName) {
		this.studentName = studentName;
	}

	public Long getStudentId() {
		return studentId;
	}

	public void setStudentId(Long studentId) {
		this.studentId = studentId;
	}

	public String getStudentName() {
		return studentName;
	}

	public void setStudentName(String studentName) {
		this.studentName = studentName;
	}

	public Set<Course> getCourses() {
		return courses;
	}

	public void setCourses(Set<Course> courses) {
		this.courses = courses;
	}

}

package hibernate_spring.example;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

public class Course implements Serializable {
	private Long courseId;
	private String courseName;

	private Set<Student> students = new HashSet<Student>(0);

	public Course() {

	}

	public Course(String courseName) {
		this.courseName = courseName;
	}

	public Set<Student> getStudents() {
		return students;
	}

	public void setStudents(Set<Student> students) {
		this.students = students;
	}

	public Long getCourseId() {
		return courseId;
	}

	public void setCourseId(Long courseId) {
		this.courseId = courseId;
	}

	public String getCourseName() {
		return courseName;
	}

	public void setCourseName(String courseName) {
		this.courseName = courseName;
	}
}

Teraz musimy tylko zmodyfikować konfigurację beana faktorii sesji tak by wczytywał stworzone właśnie pliki mapowań

<bean id="sessionFactory"
	class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
	<property name="dataSource">
		<ref local="dataSource" />
	</property>
        <!--
	<property name="exposeTransactionAwareSessionFactory">
		<value>false</value>
	</property>
        -->
	<property name="hibernateProperties">
		<ref bean="hibernateProperties" />
	</property>
	<property name="mappingLocations">
		<list>
			<value>classpath*:**/*.hbm.xml</value>
		</list>
	</property>
</bean>

Do końca brakuje już niewiele – czas na DAO


public interface CourseDao {
	void store(Course newCourse);

	void delete(Course course);

	Collection<Course> findAll();

	Course findById(long courseId);
	
	Course findByName(String courseName);
}
public interface StudentDao {
	void store(Student student);

	void delete(Student student);

	Student findById(long id);

	Collection<Student> findAll();
	
	Student findByName(String studentName);
}

i wykorzystującą je usługę CourseService

public interface CourseService {
	void enrollStudent(Course course, Student student);

	void disenrollStudent(Course course, Student student);

	Course createCourse(String courseName);

	Student createStudent(String studentName);

	void deleteCourse(Course course);

	void deleteStudent(Student student);

	Student findStudentByName(String studentName);

	Course findCourseByName(String courseName);

}

Implementacja jest bardzo prosta i w związku z tym zostanie pominięta dzięki czemu ten długi wpis nie przybierze monstrualnych rozmiarów.