Einführung in Spring Data JPA mit Hibernate

veröffentlicht am 08. September 2012

Vor kurzem habe ich mir einmal das Spring Data Neo4j Projekt angeschaut, um einen ersten Eindruck im Umgang mit der graphenorientierten Datenbanken Neo4j zu bekommen. Insgesamt finde ich den Ansatz sehr interessant, jedoch fehlt mir momentan ein Projekt, bei dem ich diese Technik einsetzen kann. Ich bin jedoch im gleichen Zuge beim Spring Data Projekt auch über Spring Data JPA gestolpert. Spring Data JPA unterstützt meines Wissens nach derzeit EclipseLink und Hibernate als JPA Implementierung. Dieser Artikel zeigt, wie man Hibernate zusammen mit Spring Data JPA verwendet.

Zunächst werden die benötigten Abhängigkeiten im Maven POM angegeben:

<dependencies> 
	<dependency>
		<groupId>org.springframework.data</groupId>
		<artifactId>spring-data-jpa</artifactId>
		<version>1.1.1.RELEASE</version>
	</dependency>
	<dependency>
		<groupId>org.hibernate</groupId>
		<artifactId>hibernate-entitymanager</artifactId>
		<version>4.1.6.Final</version>
	</dependency>
	<dependency>
		<groupId>org.hibernate</groupId>
		<artifactId>hibernate-c3p0</artifactId>
		<version>4.1.6.Final</version>
	</dependency>
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.21</version>
	</dependency>
</dependencies>

Spring Data JPA verfolgt das Domain-Driven-Design Konzept unter Verwendung des Repository Pattern (das verlinkte Beispiel ist für .NET, aber erklärt das Pattern sehr anschaulich). So wird zunächst eine Entität definiert – in diesem Fall ein einfaches Objekt, das zur Authentifizierung von Benutzern verwendet werden könnte:

package de.patrickgotthard.demo.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

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

	@Id
	@GeneratedValue
	@Column(name = "id")
	private Long id;

	@Column(name = "username")
	private String username;

	@Column(name = "password")
	private String password;

	public Long getId() {
		return id;
	}

	public void setId(final Long id) {
		this.id = id;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(final String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(final String password) {
		this.password = password;
	}

	@Override
	public String toString() {
		final StringBuilder builder = new StringBuilder();
		builder.append("User [id=");
		builder.append(id);
		builder.append(", username=");
		builder.append(username);
		builder.append(", password=");
		builder.append(password);
		builder.append("]");
		return builder.toString();
	}

}

Für dieses Entitätsobjekt  wird nun ein Repository erzeugt. Bei Spring Data JPA werden diese über Interfaces definiert, wobei sich das oberste Interface ganz einfach Repository nennt. Darunter gibt es noch drei weitere Interfaces, die diverse Funktionalitäten out of the box mitliefern (bitte entschuldigt die unkonventionelle Darstellung aus Platzgründen):

Um die größtmögliche Funktionalität mitzunehmen, wird nun also ein Repository vom Typ JpaRepository erstellt. Außerdem wird die Methode findByUsernameLike definiert (mehr dazu gleich):

package de.patrickgotthard.demo.repository;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import de.patrickgotthard.demo.entity.User;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

	List<User> findByUsernameLike(String username);

}

Spring Data JPA erkennt anhand der Methodensignatur automatisch, welche Query hier erzeugt werden muss. Von Hibernate wird später folgende Query durchgeführt:

select user0_.id as id0_, user0_.password as password0_, user0_.username as username0_ from users user0_ where user0_.username like ?

Nun wird eine passende Spring Konfiguration im Ordner src/main/resources namens applicationContext.xml erstellt. Hierbei wird auf eine ausführliche Erklärung verzichtet, die Kommentare sollten ausreichen:

<?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:tx="http://www.springframework.org/schema/tx" xmlns:jpa="http://www.springframework.org/schema/data/jpa"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
		http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.1.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">

	<!-- Activate Annotation based configuration -->
	<context:annotation-config />

	<!-- Defines where to search for annotated components -->
	<context:component-scan base-package="de.patrickgotthard.demo" />

	<!-- Database connection -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="driverClass" value="com.mysql.jdbc.Driver" />
		<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/demo" />
		<property name="user" value="" /> <!-- Set your db user here -->
		<property name="password" value="" /> <!-- Set your db password here -->
	</bean>

	<!-- Hibernate is used as JPA vendor-->
	<bean id="hibernateJpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
		<property name="generateDdl" value="true" /> <!-- Automatically create tables for the entity classes. Don't do this in production! -->
		<property name="showSql" value="false" /> <!-- Set this to true for analyzing SQL queries -->
	</bean>

	<!-- Entity manager -->
	<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter" />
		<property name="packagesToScan" value="de.patrickgotthard.demo.entity" /> <!-- Defines where the entity classes are placed -->
	</bean>

	<!-- Configure transaction management -->
	<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="entityManagerFactory" />
	</bean>
	<tx:annotation-driven transaction-manager="transactionManager" />

	<!-- Defines where repositories are placed -->
	<jpa:repositories base-package="de.patrickgotthard.demo.repository" />

</beans>

Nun kann die Funktionsfähigkeit getestet werden. Hierzu wird erst einmal folgende Klasse erstellt, die verschiedene UserRepository Aktionen ausführt:

package de.patrickgotthard.demo;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import de.patrickgotthard.demo.entity.User;
import de.patrickgotthard.demo.repository.UserRepository;

@Component
public class Executor {

	@Autowired
	private UserRepository userRepository;

	public void execute() {

		// create users
		final List<User> users = new ArrayList<User>();

		final User patrick = new User();
		patrick.setUsername("Patrick");
		patrick.setPassword("veryS3cr3t");
		users.add(patrick);

		final User john = new User();
		john.setUsername("John");
		john.setPassword("Doe");
		users.add(john);

		System.out.println("before insert (no id):");
		for (final User u : users) {
			System.out.format("  ● %s\n", u);
		}
		System.out.println();

		// persist users
		userRepository.save(users);
		System.out.println("after insert (with id):");
		for (final User u : users) {
			System.out.format("  ● %s\n", u);
		}
		System.out.println();

		// find users by username
		final String pattern = "Pa%";
		System.out.format("users with username like %s\n", pattern);
		final List<User> foundUsers = userRepository.findByUsernameLike(pattern);
		for (final User u : foundUsers) {
			System.out.format("  ● %s\n", u);
		}
		System.out.println();

		// update user
		if (!foundUsers.isEmpty()) {
			final User dbUser = foundUsers.get(0);
			dbUser.setUsername("pgotthard");
			userRepository.save(dbUser);

			final User updatedDbUser = userRepository.findOne(dbUser.getId());
			System.out.format(" after update:\n  ● %s\n\n", updatedDbUser);
		}

		// get all users from db
		System.out.println("all users:");
		for (final User u : userRepository.findAll()) {
			System.out.format("  ● %s\n", u);
		}
		System.out.println();

		// delete all users from db
		System.out.format("count before deletion: %s\n", userRepository.count());
		userRepository.deleteAll();
		System.out.format(" count after deletion: %s", userRepository.count());
	}

}

Danach muss noch eine Klasse angelegt werden, die den Spring Kontext initialisiert und die execute Methode der eben erstellten Klasse ausführt:

package de.patrickgotthard.demo;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

	public static void main(final String[] args) {

		final AbstractApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
		context.registerShutdownHook();
		final Executor executor = context.getBean(Executor.class);
		executor.execute();

	}

}

Vor dem Starten der Anwendung muss natürlich die entsprechende Datenbank angelegt werden. Beim Ausführen der Anwendung sollte nun folgende Ausgabe erscheinen:

before insert (no id):
  ● User [id=null, username=Patrick, password=veryS3cr3t]
  ● User [id=null, username=John, password=Doe]

after insert (with id):
  ● User [id=1, username=Patrick, password=veryS3cr3t]
  ● User [id=2, username=John, password=Doe]

users with username like Pa%
  ● User [id=1, username=Patrick, password=veryS3cr3t]

 after update:
  ● User [id=1, username=pgotthard, password=veryS3cr3t]

all users:
  ● User [id=1, username=pgotthard, password=veryS3cr3t]
  ● User [id=2, username=John, password=Doe]

count before deletion: 2
 count after deletion: 0

Natürlich wurden hier nur ganz einfache Datenbankoperationen demonstriert. Zur Vertiefung empfehle ich die letzten beiden der folgenden Links.

Quellen und weiterführende Links

Kommentare

Hallo Patrick,

vielen Dank für Dein ordentliches Tutorial. Ich habe leider ein Problem mit Deinem Projekt:

so findet sich in der Klasse UserRepository zum Beispiel folgender Import:

import org.springframework.stereotype.Repository;

Dieser Typ ist in keiner Deiner angegebenen Abhängigkeiten vorhanden, oder? Ich habe deswegen noch folgendes Schnipsel in der Pom hinzugefügt:

org.springframework
spring-context
3.1.3.RELEASE

Kannst Du das Project so wie es ist, mit Maven übersetzen?

Gruß aus Dortmund,

Marc

Kommentar #1 von Marc Gorzala am 15. Dezember 2012


Hallo Marc,

ich habe gerade das (damals getestete) Beispiel ausgecheckt und du hast recht, die Abhängigkeit zu Spring Context fehlt. Eventuell wurde das Maven Release mit derselben Versionsnummer überschrieben, als letztens größere Umstrukturierungen bei Spring stattfanden.

Auf jeden Fall funktioniert das Beispiel mit der neusten Spring Data JPA Version (1.2.0), dort existiert die Abhängigkeit zu Spring Context.

Gruß + danke für den Hinweis
Patrick

Kommentar #2 von Patrick am 15. Dezember 2012


Hinterlasse einen Kommentar