Beispiel zu Spring 3.1.x, Spring-Data-JPA, Hibernate 4.x, Lombok ,JUnit 4 und Maven 3

Thomas Darimont

Erfahrenes Mitglied
Hallo,

als Fortsetzung zu meinem anderen Spring Beispiel
(http://www.tutorials.de/java/356059...bernate-3-x-lombok-junit-4-und-maven-2-x.html)
hier eine aktualisierte Fassung.

Der gesamte Code findet man im Anhang. //Edit: oder auf github:
https://github.com/thomasdarimont/de.tutorials.training.spring311

Hier mal ein kleines Beispiel zur Verwendung von JPA mit Spring, Lombok ,JUnit und Maven 3.x realisiert mit
der Spring Toolsuite (STS) IDE, mysql, H2 und Tomcat 7.0.26.

Dabei verwende ich diesmal die Spring Java Config Konfigurationsvariante
(Spring Konfiguration "fast ohne" XML), und Spring-Data-JPA zur Implementierung der DAOs.

Verwendete Technologien:
  • Spring: 3.1.1.Release, Spring-Data-Jpa 1.0.3.Release
  • Hibernate: 4.0.1.Final (Hibernate -EntityManager), 4.2.0.Final (JSR-303 BeanValidation via Hibernate-Validation)
  • H2 Emebdded Database für Tests:
  • Lombok: 0.10.8
  • JUnit 4.10
  • Logback: 1.0.0
  • CDI: 1.0
  • Tomcat: 7.0.26
  • STS: 2.8.1
  • Maven: 3.0.3
  • JQuery: 1.7.1



Demonstrierte Features:
  • Spring Java Config
  • Spring Profiles
  • Spring Environment Konfiguration
  • Unit / Integration Testing mit Embedded Database (H2)
  • JPA / JSR 303 Bean Validation
  • Spring-Data-JPA
  • Spring Web MVC
  • Mehrere Maven Module
  • JQuery
  • JQuery Templating

In diesem Beispiel gibt es 3 Maven Projekte.
  1. Das Maven-Parent Projekt (de.tutorials.training.spring.parent)
  2. Die eigentliche Anwendungslogik: (de.tutorials.training.spring.application)
  3. Eine kleine Java EE 6 Webapp welche die Anwendungslogik verwendet (de.tutorials.training.spring.web)


Hier die Maven Konfigurationsdatei pom.xml des Maven-Parent Projektes:
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/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>de.tutorials</groupId>
	<artifactId>de.tutorials.training.spring.parent</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>pom</packaging>

	<properties>
		<org.springframework.version>3.1.1.RELEASE</org.springframework.version>
		<org.springframework.data.version>1.0.3.RELEASE</org.springframework.data.version>

		<org.hibernate.hibernate-entitymanager.version>4.0.1.Final</org.hibernate.hibernate-entitymanager.version>
		<org.hibernate.hibernate-validator.version>4.2.0.Final</org.hibernate.hibernate-validator.version>


		<org.junit.version>[4.,)</org.junit.version>
		<org.cglib.version>[2.,)</org.cglib.version>

		<org.slf4j.version> [1.4,)</org.slf4j.version>
		<ch.qos.logback.version>1.0.0</ch.qos.logback.version>

		<javax.enterprise.cdi-api.version>1.0</javax.enterprise.cdi-api.version>

		<org.projectlombok.lombok.version>0.10.8</org.projectlombok.lombok.version>

		<mysql.mysql-connector-java.version>[5.1.,)</mysql.mysql-connector-java.version>
		<com.h2database.version>1.3.164</com.h2database.version>
	</properties>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>mysql</groupId>
				<artifactId>mysql-connector-java</artifactId>
				<version>${mysql.mysql-connector-java.version}</version>
			</dependency>
			<dependency>
				<groupId>org.hibernate</groupId>
				<artifactId>hibernate-entitymanager</artifactId>
				<version>
					${org.hibernate.hibernate-entitymanager.version}
				</version>
			</dependency>
			<dependency>
				<groupId>org.springframework</groupId>
				<artifactId>spring-orm</artifactId>
				<version>${org.springframework.version}</version>
				<exclusions>
					<exclusion>
						<groupId>commons-logging</groupId>
						<artifactId>commons-logging</artifactId>
					</exclusion>
				</exclusions>
			</dependency>
			<dependency>
				<groupId>org.slf4j</groupId>
				<artifactId>slf4j-log4j12</artifactId>
				<version>${org.slf4j.version}</version>
			</dependency>
			<dependency>
				<groupId>cglib</groupId>
				<artifactId>cglib</artifactId>
				<version>${org.cglib.version}</version>
			</dependency>

			<dependency>
				<groupId>org.projectlombok</groupId>
				<artifactId>lombok</artifactId>
				<version>${org.projectlombok.lombok.version}</version>
			</dependency>

			<dependency>
				<groupId>org.springframework</groupId>
				<artifactId>spring-test</artifactId>
				<version>${org.springframework.version}</version>
			</dependency>
			<dependency>
				<groupId>junit</groupId>
				<artifactId>junit</artifactId>
				<version>${org.junit.version}</version>
			</dependency>
			<dependency>
				<groupId>org.springframework.data</groupId>
				<artifactId>spring-data-jpa</artifactId>
				<version>${org.springframework.data.version}</version>
			</dependency>
			<dependency>
				<groupId>javax.enterprise</groupId>
				<artifactId>cdi-api</artifactId>
				<version>${javax.enterprise.cdi-api.version}</version>
			</dependency>
			<dependency>
				<groupId>ch.qos.logback</groupId>
				<artifactId>logback-core</artifactId>
				<version>${ch.qos.logback.version}</version>
			</dependency>
			<dependency>
				<groupId>ch.qos.logback</groupId>
				<artifactId>logback-classic</artifactId>
				<version>${ch.qos.logback.version}</version>
			</dependency>
			<dependency>
				<groupId>org.hibernate</groupId>
				<artifactId>hibernate-validator</artifactId>
				<version>${org.hibernate.hibernate-validator.version}</version>
			</dependency>

			<dependency>
				<groupId>com.h2database</groupId>
				<artifactId>h2</artifactId>
				<version>${com.h2database.version}</version>
			</dependency>
		</dependencies>
	</dependencyManagement>
	<modules>
		<module>../de.tutorials.training.spring.application</module>
		<module>../de.tutorials.training.spring.webapp</module>
	</modules>
</project>


Hier die Maven Konfigurationsdatei pom.xml unseres Anwendungsprojektes (de.tutorials.training.spring.application):
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/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<artifactId>de.tutorials.training.spring.application</artifactId>

	<parent>
		<artifactId>de.tutorials.training.spring.parent</artifactId>
		<groupId>de.tutorials</groupId>
		<version>0.0.1-SNAPSHOT</version>
		<relativePath>../de.tutorials.training.spring.parent/pom.xml</relativePath>
	</parent>

	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<scope>compile</scope>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.data</groupId>
			<artifactId>spring-data-jpa</artifactId>
			<scope>compile</scope>
		</dependency>
		
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-entitymanager</artifactId>
			<scope>compile</scope>
		</dependency>

		<dependency>
			<groupId>cglib</groupId>
			<artifactId>cglib</artifactId>
			<type>jar</type>
			<scope>compile</scope>
		</dependency>


		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>compile</scope>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<scope>compile</scope>
		</dependency>


		<!-- start testing dependencies -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<type>jar</type>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<type>jar</type>
			<scope>test</scope>
		</dependency>
		<!-- end testing dependencies -->
		<dependency>
			<groupId>javax.enterprise</groupId>
			<artifactId>cdi-api</artifactId>
		</dependency>

		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-core</artifactId>
		</dependency>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-validator</artifactId>
		</dependency>
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
		</dependency>
	</dependencies>

</project>

Unser Domain Model:
Java:
package de.tutorials.app.useradmin.model;

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

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;

import lombok.Getter;
import lombok.Setter;

import org.hibernate.validator.constraints.Email;

@Entity
@Getter
@Setter
@Table(name="UA_USER")
public class User implements Serializable{
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue
    protected Long id;

    @Column(unique = true)
    protected String name;

    @Email
    @Column(unique = true)
    protected String email;

    @ManyToMany(fetch=FetchType.EAGER)
    @JoinTable(name="UA_USER_ROLE")
    protected Collection<Role> roles = new HashSet<Role>();

    public User() {
    }

    public User(String name, String email, Role... roles) {
	this.name = name;
	this.email = email;

	for (Role role : roles) {
	    this.roles.add(role);
	}
    }

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

Java:
package de.tutorials.app.useradmin.model;

import java.io.Serializable;

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

import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
@Table(name="UA_ROLE")
public class Role implements Serializable{
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue
    protected Long id;

    @Column(unique = true)
    protected String name;

    public Role() {
    }

    public Role(String name) {
	this.name = name;
    }

    @Override
    public String toString() {
	StringBuilder builder = new StringBuilder();
	builder.append("Role [id=");
	builder.append(id);
	builder.append(", name=");
	builder.append(name);
	builder.append("]");
	return builder.toString();
    }
}

Unser Service-Interface:
Java:
package de.tutorials.app.useradmin.service;

import java.util.List;

import de.tutorials.app.useradmin.model.Role;
import de.tutorials.app.useradmin.model.User;

public interface UserAdminService {
    Role register(Role role);

    User register(User user);

    List<User> findAllUsers();

    List<Role> findAllRoles();

    List<User> findUserInRole(Role role);

}

Unsere Service-Implementierung:
Java:
package de.tutorials.app.useradmin.service;

import java.util.List;

import javax.inject.Inject;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import de.tutorials.app.useradmin.model.Role;
import de.tutorials.app.useradmin.model.User;
import de.tutorials.app.useradmin.persistence.RoleRepository;
import de.tutorials.app.useradmin.persistence.UserAdminRepository;

@Service
@Transactional
public class DefaultUserAdminService implements UserAdminService {

    @Inject
    protected UserAdminRepository userRepository;

    @Inject
    protected RoleRepository roleRepository;

    @Override
    public Role register(Role role) {
	return roleRepository.save(role);
    }

    @Override
    public User register(User user) {
	return userRepository.save(user);
    }

    @Override
    public List<Role> findAllRoles() {
	return roleRepository.findAll();
    }

    @Override
    public List<User> findAllUsers() {
	return userRepository.findAll();
    }

    @Override
    public List<User> findUserInRole(Role role) {
	return userRepository.findUserByRolesName(role.getName());
    }
}

Unsere Repositories (aka DAOs).
Da wir Spring-Data-JPA verwendend brauchen wir für unsere JPA Daos keine direkten
Implementierungen mehr. Diese wird von Spring Data JPA zur Laufzeit zur Verfügung gestellt.
Mehr infos dazu gibt es hier:
http://static.springsource.org/spri...nt/reference/html/#jpa.query-methods.at-query

UserRepository-Interface
Java:
package de.tutorials.app.useradmin.persistence;

import java.util.List;

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

import de.tutorials.app.useradmin.model.User;

public interface UserAdminRepository extends JpaRepository<User, Long> {
    List<User> findUserByRolesName(String roleName);
}

Unser RoleRepository-Interface
Java:
package de.tutorials.app.useradmin.persistence;

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

import de.tutorials.app.useradmin.model.Role;

public interface RoleRepository extends JpaRepository<Role, Long>{
}

Nun komment wir zur Konfiguration unseres Moduls mit Spring Java Config:
Java:
package de.tutorials.app.useradmin;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import de.tutorials.app.useradmin.service.DefaultUserAdminService;
import de.tutorials.app.useradmin.service.UserAdminService;

@Configuration
public class UseradminModuleConfiguration {

    @Bean
    public UserAdminService userAdminService() {
	//we don't need a bean definition for this service since it is automatically picked up
	//via component scanning... we leave this bean definition just for demo purposes
	DefaultUserAdminService userAdminService = new DefaultUserAdminService();
	return userAdminService;
    }
}

Nun noch die eigentliche Spring Konfiguration mit der gesamten Infrastruktur:
Java:
package de.tutorials.app.system;

import java.sql.Driver;
import java.util.Arrays;

import javax.inject.Inject;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.hibernate4.HibernateExceptionTranslator;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
@PropertySource("classpath:/config/system.properties")
@ImportResource("classpath:/spring/spring-data-config.xml") //At the time of this writing 04.03.2012 Spring-Data-JPA is not able to be fully configured via javaconfig
						            //therefore we have to include one small xml file...
@ComponentScan(basePackages = { "de.tutorials.app" })
public class SystemModuleConfiguration {

    @Inject
    protected Environment env;

    @Bean
    public DataSource dataSource() {
	if (Arrays.asList(env.getActiveProfiles()).contains("testing")) {
	    //Wir testen mit einer embedded h2 db
	    return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).addScript("classpath:/db/schema.sql").build();
	} else {
	    SimpleDriverDataSource ds = new SimpleDriverDataSource();
	    ds.setDriverClass(env.getPropertyAsClass("persistence.db.driverClass", Driver.class));
	    ds.setUrl(env.getProperty("persistence.db.url"));
	    ds.setUsername(env.getProperty("persistence.db.username"));
	    ds.setPassword(env.getProperty("persistence.db.password"));
	    return ds;
	}
    }

    @Bean
    public JpaTransactionManager transactionManager() {
	return new JpaTransactionManager(entityManagerFactory());
    }

    @Bean
    public EntityManagerFactory entityManagerFactory() {
	LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
	em.setDataSource(dataSource());
	em.setJpaVendorAdapter(jpaVendorAdapter());
	em.setPackagesToScan("de.tutorials.app"); //this implicitly generates an appropriate persistence.xml for us at runtime . 
	em.afterPropertiesSet();
	return em.getObject();
    }

    @Bean
    public PersistenceExceptionTranslator hibernateExceptionTranslator() {
	return new HibernateExceptionTranslator();
    }

    @Bean
    public HibernateJpaVendorAdapter jpaVendorAdapter() {
	HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
	adapter.setShowSql(env.getProperty("persistence.hibernate.showSql",
		Boolean.class));
	adapter.setGenerateDdl(env.getProperty(
		"persistence.hibernate.generateDdl", Boolean.class));
	adapter.setDatabasePlatform(env
		.getProperty("persistence.hibernate.databasePlatform"));
	return adapter;
    }
}

Der JUnit-Test für UserAdminService:
Java:
package de.tutorials.app.useradmin.service;

import java.util.List;

import javax.inject.Inject;
import javax.persistence.PersistenceException;
import javax.validation.ConstraintViolationException;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

import de.tutorials.app.system.SystemModuleConfiguration;
import de.tutorials.app.useradmin.UseradminModuleConfiguration;
import de.tutorials.app.useradmin.model.Role;
import de.tutorials.app.useradmin.model.User;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;

@Transactional
@ActiveProfiles("testing")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { SystemModuleConfiguration.class,
	UseradminModuleConfiguration.class })
public class UserAdminServiceTest {

    @Inject
    UserAdminService userAdminService;

    @Test
    @Rollback(true)
    public void registerUser() {
	User user = new User();
	user.setName("User1");
	user = userAdminService.register(user);

	List<User> allUsers = userAdminService.findAllUsers();
	User savedUser = allUsers.get(0);
	assertEquals("There should be one saved user", 1, allUsers.size());
	assertEquals("The user should be equal to saved user", user, savedUser);
    }

    @Test(expected = ConstraintViolationException.class)
    @Rollback(true)
    public void registerUserWithInvalidEmailShouldFail() {
	User user = new User();
	user.setName("User1");
	user.setEmail("xxx.yyy");
	user = userAdminService.register(user);
	fail("A user with invalid email adress is not allowed!");
    }

    // @Test(expected=ConstraintViolationException.class) -> Spring Exception
    // Translation does not catch Unique Constraints on column level?!
    @Test(expected = PersistenceException.class)
    @Rollback(true)
    public void registerUserWithAlreadyExistingUsernameShouldFail() {
	User user = new User();
	user.setName("User1");
	user = userAdminService.register(user);

	user = new User();
	user.setName("User1");
	user = userAdminService.register(user);

	fail("Duplicate usernames are not allowed!");
    }

    @Test
    @Rollback(true)
    public void registerUserWithRoles() {
	User user = new User();
	user.setName("Admin");
	user.setEmail("admin@company.com");

	Role roleAdmin = new Role("admin");
	Role roleUser = new Role("user");

	roleAdmin = userAdminService.register(roleAdmin);
	roleUser = userAdminService.register(roleUser);

	user.getRoles().add(roleAdmin);
	user.getRoles().add(roleUser);

	user = userAdminService.register(user);

	List<User> allUsers = userAdminService.findAllUsers();
	User savedUser = allUsers.get(0);

	assertEquals("The user should be equal to saved user", user, savedUser);

	assertEquals("The user should have 2 roles", 2, savedUser.getRoles()
		.size());
	assertTrue("The user should have role user", savedUser.getRoles()
		.contains(roleUser));
	assertTrue("The user should have role admin", savedUser.getRoles()
		.contains(roleAdmin));
    }

    @Test
    @Rollback
    public void findAllRolesShouldReturnAllRoles() {
	userAdminService.register(new Role("foo"));
	userAdminService.register(new Role("bar"));
	userAdminService.register(new Role("user"));
	userAdminService.register(new Role("admin"));

	List<Role> roles = userAdminService.findAllRoles();
	assertEquals(4, roles.size());
    }

    @Test
    @Rollback(true)
    public void findUserByRoleName() {
	Role fooRole = userAdminService.register(new Role("foo"));
	Role barRole = userAdminService.register(new Role("bar"));

	userAdminService.register(new User("foo1", null, fooRole));
	userAdminService.register(new User("foo2", null, fooRole));
	userAdminService.register(new User("bar", null, barRole));
	userAdminService.register(new User("foo3", null, fooRole));
	userAdminService.register(new User("foobar", null, fooRole, barRole));

	List<User> foos = userAdminService.findUserInRole(fooRole);
	assertEquals(4, foos.size());

	List<User> bars = userAdminService.findUserInRole(barRole);
	assertEquals(2, bars.size());
    }
}

Unter src/main/resources/config haben wir folgende Konfigurationseinträge in system.properties:
Code:
persistence.db.driverClass=com.mysql.jdbc.Driver
persistence.db.url=jdbc\:mysql\://localhost\:3306/springapp
persistence.db.username=root
persistence.db.password=tutorials

persistence.hibernate.showSql=true
persistence.hibernate.generateDdl=true
persistence.hibernate.databasePlatform=org.hibernate.dialect.MySQL5InnoDBDialect

Unter src/main/resources/spring haben wir die einzige Spring Konfiguration im XML-Format.
Spring-Data-JPA lässt sich derzeit (04.03.2012) nicht voll über Spring Java Config Konfigurieren.
:
XML:
<?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:aop="http://www.springframework.org/schema/aop"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:jee="http://www.springframework.org/schema/jee" xmlns:lang="http://www.springframework.org/schema/lang"
	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util"
	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.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd
        http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
        http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"
	default-autowire="byName">


	<jpa:repositories base-package="de.tutorials.app"/>

</beans>

Unser Schema für die embedded h2 Datenbank schemasrc/test/resources/schema.sql
SQL:
CREATE TABLE ua_role (
  id identity NOT NULL,
  name varchar(255) not NULL,
  PRIMARY KEY (id),
  UNIQUE KEY ua_role_name (name)
);

CREATE TABLE ua_user (
  id identity NOT NULL,
  email varchar(255) default NULL,
  name varchar(255) not NULL,
  PRIMARY KEY (id),
  UNIQUE KEY ua_user_email (email),
  UNIQUE KEY ua_user_name (name)
);

CREATE TABLE ua_user_role (
  UA_USER_id bigint(20) NOT NULL,
  roles_id bigint(20) NOT NULL,
  UNIQUE KEY unique_user_role (UA_USER_id, roles_id)
);

ALTER TABLE ua_user_role ADD FOREIGN KEY (roles_id) REFERENCES ua_role (id);
ALTER TABLE ua_user_role ADD FOREIGN KEY (UA_USER_id) REFERENCES ua_user (id);


Hier noch die Konfigurationsdatei für das Logback Logging-Framework:
XML:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>

	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">

		<encoder>
			<pattern>
				%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
			</pattern>
		</encoder>
	</appender>

	<root level="INFO">
		<appender-ref ref="STDOUT" />
	</root>

</configuration>

Hier nun die Maven Konfigurationsdatei pom.xml unserer Webapplication:
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/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<artifactId>de.tutorials.training.spring.webapp</artifactId>

	<parent>
		<artifactId>de.tutorials.training.spring.parent</artifactId>
		<groupId>de.tutorials</groupId>
		<version>0.0.1-SNAPSHOT</version>
		<relativePath>../de.tutorials.training.spring.parent/pom.xml</relativePath>
	</parent>


	<packaging>war</packaging>

	<name>de.tutorials.training.spring.webapp</name>

	<properties>
		<endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<dependencies>
	
		<dependency>
			<groupId>javax</groupId>
			<artifactId>javaee-web-api</artifactId>
			<version>6.0</version>
			<scope>provided</scope>
		</dependency>
		
				
		<dependency>
			<groupId>de.tutorials</groupId>
			<artifactId>
        		de.tutorials.training.spring.application
        	</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>3.1.1.RELEASE</version>
		</dependency>
		
		<!-- Jackson JSON Mapper -->
		<dependency>
			<groupId>org.codehaus.jackson</groupId>
			<artifactId>jackson-mapper-asl</artifactId>
			<version>[1.9.,)</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>1.6</source>
					<target>1.6</target>
					<compilerArguments>
						<endorseddirs>${endorsed.dir}</endorseddirs>
					</compilerArguments>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-war-plugin</artifactId>
				<version>2.1.1</version>
				<configuration>
					<failOnMissingWebXml>false</failOnMissingWebXml>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-dependency-plugin</artifactId>
				<version>2.1</version>
				<executions>
					<execution>
						<phase>validate</phase>
						<goals>
							<goal>copy</goal>
						</goals>
						<configuration>
							<outputDirectory>${endorsed.dir}</outputDirectory>
							<silent>true</silent>
							<artifactItems>
								<artifactItem>
									<groupId>javax</groupId>
									<artifactId>javaee-endorsed-api</artifactId>
									<version>6.0</version>
									<type>jar</type>
								</artifactItem>
							</artifactItems>
						</configuration>
					</execution>
				</executions>
			</plugin>
		</plugins>
		<pluginManagement>
			<plugins>
				<!--This plugin's configuration is used to store Eclipse m2e settings 
					only. It has no influence on the Maven build itself. -->
				<plugin>
					<groupId>org.eclipse.m2e</groupId>
					<artifactId>lifecycle-mapping</artifactId>
					<version>1.0.0</version>
					<configuration>
						<lifecycleMappingMetadata>
							<pluginExecutions>
								<pluginExecution>
									<pluginExecutionFilter>
										<groupId>
											org.apache.maven.plugins
										</groupId>
										<artifactId>
											maven-dependency-plugin
										</artifactId>
										<versionRange>
											[2.1,)
										</versionRange>
										<goals>
											<goal>copy</goal>
										</goals>
									</pluginExecutionFilter>
									<action>
										<ignore></ignore>
									</action>
								</pluginExecution>
							</pluginExecutions>
						</lifecycleMappingMetadata>
					</configuration>
				</plugin>
			</plugins>
		</pluginManagement>
	</build>

</project>

Auch im Web Projekt verwenden wir keine XML Konfiguration. Das setzt allerdings voraus, dass ein
Servlet 3.0 fähiger Servlet Container verwendet wird!

Unser Spring / Spring WebMVC Bootstrap welcher die Spring Umgebung im Servlet Container startet.
Java:
package de.tutorials.app.web;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.DispatcherServlet;

public class Bootstrap implements WebApplicationInitializer {

    AnnotationConfigWebApplicationContext rootContext;

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
	setupApplicationContext();
	registerServlets(servletContext);
	registerListeners(servletContext);
	registerFilters(servletContext);
    }

    private void registerServlets(ServletContext servletContext) {
	     // Create the dispatcher servlet's Spring application context
	      AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
	      dispatcherContext.register(DispatcherConfig.class);

	      // Register and map the dispatcher servlet
	      ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
	      dispatcher.setLoadOnStartup(1);
	      dispatcher.addMapping("/dispatch/*");
    }

    private void setupApplicationContext() {
	this.rootContext = new AnnotationConfigWebApplicationContext();
	this.rootContext.scan("de.tutorials.app");
	//this.rootContext.register(annotatedClasses)
    }

    private void registerListeners(ServletContext servletContext) {
	servletContext.addListener(new ContextLoaderListener(this.rootContext ));
    }

    private void registerFilters(ServletContext servletContext) {
	servletContext.addFilter("hiddenHttpMethodFilter", HiddenHttpMethodFilter.class).addMappingForUrlPatterns(null, false, "/*");
    }
}

Hier unsere Konfiguration des MVC Dispatchers:
Java:
package de.tutorials.app.web;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@EnableWebMvc
@Configuration
@ComponentScan(basePackageClasses = { DispatcherConfig.class })
public class DispatcherConfig {
}

Hier unserer Controller zu unserem UserAdminService:
Java:
package de.tutorials.app.web.rest;

import java.util.Date;
import java.util.List;

import javax.inject.Inject;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import de.tutorials.app.useradmin.model.User;
import de.tutorials.app.useradmin.service.UserAdminService;

@Controller
@RequestMapping("/useradmin")
public class UserAdminController {
    @Inject
    protected UserAdminService userAdminService;

    @RequestMapping(method = { RequestMethod.POST }, value = "/registeruser", consumes = { "application/json" }, produces = { "application/json" })
    @ResponseBody
    public User registerUser(@RequestBody User user) {
	user.setId(null);
	return userAdminService.register(user);
    }

    @RequestMapping(method = { RequestMethod.GET }, value = "/listusers", produces = { "application/json" })
    public @ResponseBody
    List<User> listUsers() {
	return userAdminService.findAllUsers();
    }

    @RequestMapping(value = "ping")
    public void ping() {
	System.out.println("ping " + new Date());
    }
}


Die index.html implementiert das Frontend zum UserAdminController
HTML:
<!DOCTYPE HTML>

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Tutorials Spring App</title>
<script type="text/javascript" src="res/3rdparty/jq/jquery-1.7.1.min.js"></script>
<script type="text/javascript" src="res/3rdparty/jq/jquery.tmpl.min.js"></script>

<script type="text/javascript" src="res/3rdparty/json2/json2.js"></script>


<script id="userentryTemplate" type="text/x-jquery-tmpl">
	<tr>
		<td>${name}</td>
		<td>${email}</td>
	</tr>
</script>

<script type="text/javascript" src="res/js/app.js"></script>
</head>
<body>
	<h1>Welcome Tutorials Spring App</h1>

	<form id="addUserForm">
		<label for="txtUsername">Username:&nbsp;</label><input type="text"id="txtUsername" /><br /> 
		<label for="txtEmail">Email:&nbsp;</label><input type="email" id="txtEmail" /><br />
		<input type="submit" value="Register" />
	</form>

	<div id="userlist">
		<table border="1">
			<thead>
				<tr>
					<th>Username</th>
					<th>Email</th>
				</tr>
			</thead>
			<tbody id="userlistBody">
			</tbody>
		</table>
	</div>
</body>

</html>

Unsere Fronted Logik app.js:
Javascript:
	var refreshUsersList = function(users) {
		var $userEntryTemplate = $("#userentryTemplate").template();
		$("#userlistBody").empty();
		$.tmpl($userEntryTemplate, users).appendTo("#userlistBody");
	};

	var useradminAPI = {

		createRequest : function(type, urlSuffix, data) {
			var request = {
				url : "dispatch/" + urlSuffix,
				type : type,
				data : data ? JSON.stringify(data) : null,
				contentType : "application/json",
				dataType : "json"
			};
			return request;
		}

		,
		registerUser : function(user) {
			var request = useradminAPI.createRequest("POST",
					"useradmin/registeruser", user);

			$.ajax(request).done(function(reponse) {
				useradminAPI.findAllUsers();
			});
		},
		findAllUsers : function() {
			var request = useradminAPI.createRequest("GET",
					"useradmin/listusers");

			$.ajax(request).done(function(users) {
				refreshUsersList(users);
			});

		}
	};

	var init = function() {
		$("#addUserForm").submit(function() {

			var user = {
				name : $("#txtUsername").val(),
				email : $("#txtEmail").val()
			};

			useradminAPI.registerUser(user);

			return false;
		});

		useradminAPI.findAllUsers();

	};

	$(init);

Nachdem man die Projekte aus dem Zip-Archiv in Eclipse importiert hat muss man gegebenenfalls für die Projekte die Maven Konfiguration aktualisieren (Projekt -> Kontextmenü -> Maven -> Update Project Configuration).

Wenn man das Projekt anschließend auf einem Servlet Container über Eclipse ausführen möchte muss einfach das WebProjekt per Drag & Drop auf den zuvor definierten Tomcat Server ziehen.

Nun startet man die Webanwendung über das Kontextmenü -> Run As -> Run on Server und wählt dort die Tomcat-Serverkonfiguration aus, auf die man zuvor das Projekt deployed hat.


Anschließend kann man die Web-Anwendung über die URL:
http://localhost:TOMCAT_PORT/de.tutorials.training.spring.webapp/
aufrufen.

Gruß Tom
 

Anhänge

Zuletzt bearbeitet von einem Moderator:
Hi Thomas,

ich erhalte beim "mvn install" leider folgende Fehlermeldung:

[FATAL] Non-resolvable parent POM: Could not find artifact de.tutorials:de.tutorials.training.spring.parent:pom:0.0.1-SNAPSHOT and 'parent.relativePath' points at wrong local POM @ line 6, column 10

...

[ERROR] The project de.tutorials:de.tutorials.training.spring.application:0.0.1-SNAPSHOT (C:\Users\Logan\workspace\de.tutorials.training.spring.application\pom.xml) has 1 error
[ERROR] Non-resolvable parent POM: Could not find artifact de.tutorials:de.tutorials.training.spring.parent:pom:0.0.1-SNAPSHOT and 'parent.relativePath' points at wrong local POM @ line 6, column 10 -> [Help 2]
org.apache.maven.model.resolution.UnresolvableModelException: Could not find artifact de.tutorials:de.tutorials.training.spring.parent:pom:0.0.1-SNAPSHOT

...

Caused by: org.sonatype.aether.resolution.ArtifactResolutionException: Could not find artifact de.tutorials:de.tutorials.training.spring.parent:pom:0.0.1-SNAPSHOT

Ich weiß gerade nicht wie ich es lösen kann, da ich mich in Eclipse durch die POMs navigieren kann?!
Hast du eine Idee?
 
Hallo,

welche Maven Version verwendest du? Welche Eclipse Version verwendest du?
Liegen die Projekte alle im selben Verzeichnis?

Gruß Tom
 
Hi,

Maven Version: 3.0.4
Eclipse Version: Indigo Service Release 2

Im Filesystem liegen sie alle unter:
...\spring311-javaconfig-beispiel
also
...\spring311-javaconfig-beispiel\de.tutorials.training.spring.application
...\spring311-javaconfig-beispiel\de.tutorials.training.spring.parent
...\spring311-javaconfig-beispiel\de.tutorials.training.spring.webapp

In Eclipse werden alle Module als ein Projekt dargestellt.
Müssten Application und Webapp nicht im Parent enthalten sein?
 
Hallo,

nein ... die anderen Maven Projekte können dank der relativen Pfad Angabe auch nebeneinander liegen.

Welche Maven Version verwendest du? Ich würde dir empfehlen die Spring Source Toolsuite (STS( Eclipse Distribution zu verwenden. http://www.springsource.com/developer/sts

Dort sind alle Plugins / Libs in den passenden Versionen enthalten. STS 2.8.1 basiert auf Eclipse 3.7.1 mit JEE Extensions.

Gruß Tom
 
Ich nutze Maven: 3.0.4

Hab es runtergeladen und erhalte den selben Fehler :( In der Console steht noch folgendes:

[ERROR] The project de.tutorials:de.tutorials.training.spring.application:0.0.1-SNAPSHOT (C:\...\spring311-javaconfig-beispiel\de.tutorials.training.spring.application\pom.xml) has 1 error
[ERROR] Non-resolvable parent POM: Could not find artifact de.tutorials:de.tutorials.training.spring.parent:pom:0.0.1-SNAPSHOT and 'parent.relativePath' points at wrong local POM @ line 6, column 10 -> [Help 2]

Wenn ich es als Maven Projekt importieren möchte, sieht es so aus:

Eclipse.jpg

Magst du es sonst einmal bei dir runterladen und versuchen?
 
Hallo,

verwendest du dann auch eine entsprechend neue Version des Maven Eclipse Plugins (M2Eclipse)?

ich schau heute Abend nochmal danach.
Eine andere Möglichkeit ... importiere die Projekte mal als "normale" Projekte.
Import -> Import existing projects. Anschließend mach mal auf jedem Projekt Kontextmenü -> Maven -> Update Project Configuration

Gruß Tom
 
Hey,

ich denke, dass es unabhängig von der M2Eclipse-Version ist, da ich schließlich in der "cmd" ebenfalls Fehler erhalte! Würde mich freuen, wenn du dir die Zeit nehmen könntest!

Viele Grüße
Michi
 
Hallo,

in den Projekten application und webapp haben in der pom.xml in den parent Definitionen die relativenPath Settings gefehlt:

XML:
...
	<parent>
		<artifactId>de.tutorials.training.spring.parent</artifactId>
		<groupId>de.tutorials</groupId>
		<version>0.0.1-SNAPSHOT</version>
		<relativePath>../de.tutorials.training.spring.parent/pom.xml</relativePath>
	</parent>
...

Ich habe die Einträge oben im Beispiel angepasst und auch zu github gepushed.

Code:
C:\development\repos\git\de.tutorials.training.spring311\de.tutorials.training.spring.parent>mvn clean install
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] de.tutorials.training.spring.parent
[INFO] de.tutorials.training.spring.application
[INFO] de.tutorials.training.spring.webapp
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building de.tutorials.training.spring.parent 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.4.1:clean (default-clean) @ de.tutorials.training.spring.parent ---
[INFO]
....

Gruß Tom
 
Zuletzt bearbeitet von einem Moderator:
Zurück