Categories: springmvc

Spring 4 MVC+JPA2+Hibernate Many-to-many-Example

In this post, we will discuss about JPA2 with Spring and create a full JPA2 complaint persistence layer for our Spring 4 MVC based web application, showing Many-to-many association relationship, right from view till persistence. That means you can choose appropriate JPA2 provider [Hibernate, EclipseLink,..] and later switch between them with only minor configuration changes. We will use Hibernate as the persistence provider, you may decide to use other ones. Let’s get going.

This post resembles Spring 4 MVC+Hibernate Many-to-many JSP Example. Only difference between these two is that current one is pure JPA, while the previous one was specific to Hibernate. That means you can just take a look at configuration [JPAConfiguration.java] and the DAO classes[AbstractDao,UserDaoImpl & UserProfileSDaoImpl.java] from this post, and skip the rest. For those who want complete example here itself,may stick a bit longer with me.


Following technologies being used:

  • Spring 4.3.0.RELEASE
  • Hibernate Core 4.3.10.Final
  • validation-api 1.1.0.Final
  • hibernate-validator 5.1.3.Final
  • MySQL Server 5.6
  • Maven 3
  • JDK 1.7
  • Tomcat 8.0.21
  • Eclipse MARS.1 Release 4.5

Hi JPA, Hey Spring

In JPA, EntityManager is the primary interface used to interact with the persistence context managing your entities. EntityManager instances itself are generated using JPA EntityManagerFactory. Spring provides three different ways to setting up EntityManagerFactory:

  • LocalEntityManagerFactoryBean
  • Obtaining an EntityManagerFactory from JNDI
  • LocalContainerEntityManagerFactoryBean

Spring’s recommends LocalContainerEntityManagerFactoryBean as the most powerful and complete option for setting up JPA EntityManagerFactory in any environment.

JPA Configuration

Shown below is the configuration class setting up JPA. Take special note of setJpaVendorAdapter(jpaVendorAdapter()) & setJpaProperties(jpaProperties()). These two methods provides us the hooks to specify JPA provider specific properties. In our case it is hibernate. That means if tomorrow you want to use EclipseLink , you should be updating jpaVendorAdapter to use EclipseLinkJpaVendorAdapter and corresponding EclipseLink specific properties.

Additionally, thanks to factoryBean.setPackagesToScan(path), Spring can scan the annotated classes for Entities, means persistence.xml is not necessarily required.

Finally, we are referring to JpaTransactionManager instead of any implementation specific transaction manager.

package com.websystique.springmvc.configuration;

import java.util.Properties;

import javax.naming.NamingException;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
@PropertySource(value = { "classpath:application.properties" })
public class JpaConfiguration {

 @Autowired
 private Environment environment;

 @Bean
 public DataSource dataSource() {
  DriverManagerDataSource dataSource = new DriverManagerDataSource();
  dataSource.setDriverClassName(environment.getRequiredProperty("jdbc.driverClassName"));
  dataSource.setUrl(environment.getRequiredProperty("jdbc.url"));
  dataSource.setUsername(environment.getRequiredProperty("jdbc.username"));
  dataSource.setPassword(environment.getRequiredProperty("jdbc.password"));
  return dataSource;
 }

 @Bean
 public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws NamingException {
  LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
  factoryBean.setDataSource(dataSource());
  factoryBean.setPackagesToScan(new String[] { "com.websystique.springmvc.model" });
  factoryBean.setJpaVendorAdapter(jpaVendorAdapter());
  factoryBean.setJpaProperties(jpaProperties());
  return factoryBean;
 }

 /*
  * Provider specific adapter.
  */ @Bean
 public JpaVendorAdapter jpaVendorAdapter() {
  HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
  return hibernateJpaVendorAdapter;
 }

 /*
  * Here you can specify any provider specific properties.
  */ private Properties jpaProperties() {
  Properties properties = new Properties();
  properties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));
  // properties.put("hibernate.hbm2ddl.auto", environment.getRequiredProperty("hibernate.hbm2ddl.auto"));
  properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
  properties.put("hibernate.format_sql", environment.getRequiredProperty("hibernate.format_sql"));
  return properties;
 }

 @Bean
 @Autowired
 public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
  JpaTransactionManager txManager = new JpaTransactionManager();
  txManager.setEntityManagerFactory(emf);
  return txManager;
 }

}

That’s all you need on the configuration side for JPA2. Application properties used in above configuration are shown below.
application.properties

jdbc.driverClassName = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/websystique
jdbc.username = myuser
jdbc.password = mypassword
hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
hibernate.hbm2ddl.auto=create-drop
hibernate.show_sql = true
hibernate.format_sql = true

DAO Layer setup

Once the Configuration setup is done, we can finally start using EntityManager in your persistence logic. Shown below is the top-level abstract class we will be using for all our DAO related operations. Using @PersistenceContext, we are injecting container-managed EntityManager which will eventually be used for all [CRUD] operations.

package com.websystique.springmvc.dao;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

public abstract class AbstractDao<PK extends Serializable, T> {
 
 private final Class<T> persistentClass;
 
 @SuppressWarnings("unchecked")
 public AbstractDao(){
  this.persistentClass =(Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1];
 }
 
 @PersistenceContext
 EntityManager entityManager;
 
 protected EntityManager getEntityManager(){
  return this.entityManager;
 }

 protected T getByKey(PK key) {
  return (T) entityManager.find(persistentClass, key);
 }

 protected void persist(T entity) {
  entityManager.persist(entity);
 }
 
 protected void update(T entity) {
  entityManager.merge(entity);
 }

 protected void delete(T entity) {
  entityManager.remove(entity);
 }

}

Show below are the application specific DAO artifacts used in this example.

package com.websystique.springmvc.dao;

import java.util.List;

import com.websystique.springmvc.model.User;


public interface UserDao {

 User findById(int id);
 
 User findBySSO(String sso);
 
 void save(User user);
 
 void deleteBySSO(String sso);
 
 List<User> findAllUsers();

}
package com.websystique.springmvc.dao;

import java.util.Collection;
import java.util.List;

import javax.persistence.NoResultException;

import org.springframework.stereotype.Repository;

import com.websystique.springmvc.model.User;


@Repository("userDao")
public class UserDaoImpl extends AbstractDao<Integer, User> implements UserDao {

 public User findById(int id) {
  User user = getByKey(id);
  if(user!=null){
   initializeCollection(user.getUserProfiles());
  }
  return user;
 }

 public User findBySSO(String sso) {
  System.out.println("SSO : "+sso);
  try{
   User user = (User) getEntityManager()
     .createQuery("SELECT u FROM User u WHERE u.ssoId LIKE :ssoId")
     .setParameter("ssoId", sso)
     .getSingleResult();
   
   if(user!=null){
    initializeCollection(user.getUserProfiles());
   }
   return user; 
  }catch(NoResultException ex){
   return null;
  }
 }
 
 @SuppressWarnings("unchecked")
 public List<User> findAllUsers() {
  List<User> users = getEntityManager()
    .createQuery("SELECT u FROM User u ORDER BY u.firstName ASC")
    .getResultList();
  return users;
 }

 public void save(User user) {
  persist(user);
 }

 public void deleteBySSO(String sso) {
  User user = (User) getEntityManager()
    .createQuery("SELECT u FROM User u WHERE u.ssoId LIKE :ssoId")
    .setParameter("ssoId", sso)
    .getSingleResult();
  delete(user);
 }
 //An alternative to Hibernate.initialize()
 protected void initializeCollection(Collection<?> collection) {
     if(collection == null) {
         return;
     }
     collection.iterator().hasNext();
 }

}

Main highlights of this implementations are

  • Use of EntityManager
  • Use of Java Persistence Query Language [JPQL]
  • Protection against LazyInitializationExceptionby initializing the collection beforehand.
package com.websystique.springmvc.dao;

import java.util.List;

import com.websystique.springmvc.model.UserProfile;


public interface UserProfileDao {

 List<UserProfile> findAll();
 
 UserProfile findByType(String type);
 
 UserProfile findById(int id);
}
package com.websystique.springmvc.dao;

import java.util.List;

import javax.persistence.NoResultException;

import org.springframework.stereotype.Repository;

import com.websystique.springmvc.model.UserProfile;



@Repository("userProfileDao")
public class UserProfileDaoImpl extends AbstractDao<Integer, UserProfile>implements UserProfileDao{

 public UserProfile findById(int id) {
  return getByKey(id);
 }

 public UserProfile findByType(String type) {
  System.out.println("type: "+type);
  try{
   UserProfile userProfile = (UserProfile) getEntityManager()
     .createQuery("SELECT p FROM UserProfile p WHERE p.type LIKE :type")
     .setParameter("type", type)
     .getSingleResult();
   return userProfile; 
  }catch(NoResultException ex){
   return null;
  }
 }
 
 @SuppressWarnings("unchecked")
 public List<UserProfile> findAll(){
  List<UserProfile> userProfiles = getEntityManager()
    .createQuery("SELECT p FROM UserProfile p  ORDER BY p.type ASC")
    .getResultList();
  return userProfiles;
 }
 
}

That’s all for JPA specifics. Rest of the post is a typical Spring 4 MVC application setup. You may skip or continue.


Complete code example

Create schema for Many-To-Many association with Join table

APP_USER : Contains Users. A User can have several profiles[ USER,ADMIN,DBA].
USER_PROFILE : Contains User Profiles. A Profile can be linked to several users.
APP_USER_USER_PROFILE : It’s a Join table linking APP_USER & USER_PROFILE in Many-To-Many relationship.

For demonstration purpose, We will discuss Many-to-Many unidirectional [User to UserProfile] setup in this example.

create table APP_USER (
   id BIGINT NOT NULL AUTO_INCREMENT,
   sso_id VARCHAR(30) NOT NULL,
   password VARCHAR(100) NOT NULL,
   first_name VARCHAR(30) NOT NULL,
   last_name  VARCHAR(30) NOT NULL,
   email VARCHAR(30) NOT NULL,
   PRIMARY KEY (id),
   UNIQUE (sso_id)
);
 
 
create table USER_PROFILE(
   id BIGINT NOT NULL AUTO_INCREMENT,
   type VARCHAR(30) NOT NULL,
   PRIMARY KEY (id),
   UNIQUE (type)
);
 
 
CREATE TABLE APP_USER_USER_PROFILE (
    user_id BIGINT NOT NULL,
    user_profile_id BIGINT NOT NULL,
    PRIMARY KEY (user_id, user_profile_id),
    CONSTRAINT FK_APP_USER FOREIGN KEY (user_id) REFERENCES APP_USER (id),
    CONSTRAINT FK_USER_PROFILE FOREIGN KEY (user_profile_id) REFERENCES USER_PROFILE (id)
);

/* Populate USER_PROFILE Table */INSERT INTO USER_PROFILE(type)
VALUES ('USER');
 
INSERT INTO USER_PROFILE(type)
VALUES ('ADMIN');
 
INSERT INTO USER_PROFILE(type)
VALUES ('DBA');

commit;

For any help with MySQL & database setup , please visit MySQL installation on Local PC.

Project Structure

pom.xml

<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
 xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

 <modelVersion>4.0.0</modelVersion>
 <groupId>com.websystique.springmvc</groupId>
 <artifactId>SpringMVCJPA2HibernateManyToManyCRUDExample</artifactId>
 <packaging>war</packaging>
 <version>1.0.0</version>
 <name>SpringMVCJPA2HibernateManyToManyCRUDExample</name>

   <properties>
  <springframework.version>4.3.0.RELEASE</springframework.version>
  <hibernate.version>4.3.10.Final</hibernate.version>
  <mysql.connector.version>5.1.31</mysql.connector.version>
 </properties>

 <dependencies>

  <!-- Spring -->
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-core</artifactId>
   <version>${springframework.version}</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-web</artifactId>
   <version>${springframework.version}</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>${springframework.version}</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-tx</artifactId>
   <version>${springframework.version}</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-orm</artifactId>
   <version>${springframework.version}</version>
  </dependency>

  <!-- Hibernate -->
  <dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-core</artifactId>
   <version>${hibernate.version}</version>
  </dependency>
  <dependency>
         <groupId>org.hibernate</groupId>
         <artifactId>hibernate-entitymanager</artifactId>
         <version>${hibernate.version}</version>
     </dependency>

  <!-- jsr303 validation -->
  <dependency>
   <groupId>javax.validation</groupId>
   <artifactId>validation-api</artifactId>
   <version>1.1.0.Final</version>
  </dependency>
  <dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-validator</artifactId>
   <version>5.1.3.Final</version>
  </dependency>

  <!-- MySQL -->
  <dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>${mysql.connector.version}</version>
  </dependency>

  <!-- Servlet+JSP+JSTL -->
  <dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>javax.servlet-api</artifactId>
   <version>3.1.0</version>
  </dependency>
  <dependency>
   <groupId>javax.servlet.jsp</groupId>
   <artifactId>javax.servlet.jsp-api</artifactId>
   <version>2.3.1</version>
  </dependency>
  <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
  </dependency>
  
 </dependencies>

 <build>
  <pluginManagement>
   <plugins>
    <plugin>
     <groupId>org.apache.maven.plugins</groupId>
     <artifactId>maven-compiler-plugin</artifactId>
     <version>3.2</version>
     <configuration>
      <source>1.7</source>
      <target>1.7</target>
     </configuration>
    </plugin>
    <plugin>
     <groupId>org.apache.maven.plugins</groupId>
     <artifactId>maven-war-plugin</artifactId>
     <version>2.4</version>
     <configuration>
      <warSourceDirectory>src/main/webapp</warSourceDirectory>
      <warName>SpringMVCJPA2HibernateManyToManyCRUDExample</warName>
      <failOnMissingWebXml>false</failOnMissingWebXml>
     </configuration>
    </plugin>
   </plugins>
  </pluginManagement>
  <finalName>SpringMVCJPA2HibernateManyToManyCRUDExample</finalName>
 </build>
</project>

Models/Entities

package com.websystique.springmvc.model;

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

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

import org.hibernate.validator.constraints.NotEmpty;

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

 @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Integer id;

 @NotEmpty
 @Column(name="SSO_ID", unique=true, nullable=false)
 private String ssoId;
 
 @NotEmpty
 @Column(name="PASSWORD", nullable=false)
 private String password;
  
 @NotEmpty
 @Column(name="FIRST_NAME", nullable=false)
 private String firstName;

 @NotEmpty
 @Column(name="LAST_NAME", nullable=false)
 private String lastName;

 @NotEmpty
 @Column(name="EMAIL", nullable=false)
 private String email;

 @NotEmpty
 @ManyToMany(fetch = FetchType.LAZY)
 @JoinTable(name = "APP_USER_USER_PROFILE", 
             joinColumns = { @JoinColumn(name = "USER_ID") }, 
             inverseJoinColumns = { @JoinColumn(name = "USER_PROFILE_ID") })
 private Set<UserProfile> userProfiles = new HashSet<UserProfile>();

 public Integer getId() {
  return id;
 }

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

 public String getSsoId() {
  return ssoId;
 }

 public void setSsoId(String ssoId) {
  this.ssoId = ssoId;
 }

 public String getPassword() {
  return password;
 }

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

 public String getFirstName() {
  return firstName;
 }

 public void setFirstName(String firstName) {
  this.firstName = firstName;
 }

 public String getLastName() {
  return lastName;
 }

 public void setLastName(String lastName) {
  this.lastName = lastName;
 }

 public String getEmail() {
  return email;
 }

 public void setEmail(String email) {
  this.email = email;
 }

 public Set<UserProfile> getUserProfiles() {
  return userProfiles;
 }

 public void setUserProfiles(Set<UserProfile> userProfiles) {
  this.userProfiles = userProfiles;
 }

 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + ((id == null) ? 0 : id.hashCode());
  result = prime * result + ((ssoId == null) ? 0 : ssoId.hashCode());
  return result;
 }

 @Override
 public boolean equals(Object obj) {
  if (this == obj)
   return true;
  if (obj == null)
   return false;
  if (!(obj instanceof User))
   return false;
  User other = (User) obj;
  if (id == null) {
   if (other.id != null)
    return false;
  } else if (!id.equals(other.id))
   return false;
  if (ssoId == null) {
   if (other.ssoId != null)
    return false;
  } else if (!ssoId.equals(other.ssoId))
   return false;
  return true;
 }

 @Override
 public String toString() {
  return "User [id=" + id + ", ssoId=" + ssoId + ", password=" + password
    + ", firstName=" + firstName + ", lastName=" + lastName
    + ", email=" + email + "]";
 }


 
}
package com.websystique.springmvc.model;

import java.io.Serializable;

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

@Entity
@Table(name="USER_PROFILE")
public class UserProfile implements Serializable{

 @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Integer id; 

 @Column(name="TYPE", length=15, unique=true, nullable=false)
 private String type = UserProfileType.USER.getUserProfileType();
 
 public Integer getId() {
  return id;
 }

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

 public String getType() {
  return type;
 }

 public void setType(String type) {
  this.type = type;
 }

 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + ((id == null) ? 0 : id.hashCode());
  result = prime * result + ((type == null) ? 0 : type.hashCode());
  return result;
 }

 @Override
 public boolean equals(Object obj) {
  if (this == obj)
   return true;
  if (obj == null)
   return false;
  if (!(obj instanceof UserProfile))
   return false;
  UserProfile other = (UserProfile) obj;
  if (id == null) {
   if (other.id != null)
    return false;
  } else if (!id.equals(other.id))
   return false;
  if (type == null) {
   if (other.type != null)
    return false;
  } else if (!type.equals(other.type))
   return false;
  return true;
 }

 @Override
 public String toString() {
  return "UserProfile [id=" + id + ", type=" + type + "]";
 }




}
package com.websystique.springmvc.model;

public enum UserProfileType {
 USER("USER"),
 DBA("DBA"),
 ADMIN("ADMIN");
 
 String userProfileType;
 
 private UserProfileType(String userProfileType){
  this.userProfileType = userProfileType;
 }
 
 public String getUserProfileType(){
  return userProfileType;
 }
 
}

Service Layer setup

package com.websystique.springmvc.service;

import java.util.List;

import com.websystique.springmvc.model.UserProfile;


public interface UserProfileService {

 UserProfile findById(int id);

 UserProfile findByType(String type);
 
 List<UserProfile> findAll();
 
}
package com.websystique.springmvc.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.websystique.springmvc.dao.UserProfileDao;
import com.websystique.springmvc.model.UserProfile;


@Service("userProfileService")
@Transactional
public class UserProfileServiceImpl implements UserProfileService{
 
 @Autowired
 UserProfileDao dao;
 
 public UserProfile findById(int id) {
  return dao.findById(id);
 }

 public UserProfile findByType(String type){
  return dao.findByType(type);
 }

 public List<UserProfile> findAll() {
  return dao.findAll();
 }
}
package com.websystique.springmvc.service;

import java.util.List;

import com.websystique.springmvc.model.User;


public interface UserService {
 
 User findById(int id);
 
 User findBySSO(String sso);
 
 void saveUser(User user);
 
 void updateUser(User user);
 
 void deleteUserBySSO(String sso);

 List<User> findAllUsers(); 
 
 boolean isUserSSOUnique(Integer id, String sso);

}
package com.websystique.springmvc.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.websystique.springmvc.dao.UserDao;
import com.websystique.springmvc.model.User;


@Service("userService")
@Transactional
public class UserServiceImpl implements UserService{

 @Autowired
 private UserDao dao;

 public User findById(int id) {
  return dao.findById(id);
 }

 public User findBySSO(String sso) {
  User user = dao.findBySSO(sso);
  return user;
 }

 public void saveUser(User user) {
  dao.save(user);
 }

 /*
  * Since the method is running with Transaction, No need to call hibernate update explicitly.
  * Just fetch the entity from db and update it with proper values within transaction.
  * It will be updated in db once transaction ends. 
  */ public void updateUser(User user) {
  User entity = dao.findById(user.getId());
  if(entity!=null){
   entity.setSsoId(user.getSsoId());
   entity.setPassword(user.getPassword());
   entity.setFirstName(user.getFirstName());
   entity.setLastName(user.getLastName());
   entity.setEmail(user.getEmail());
   entity.setUserProfiles(user.getUserProfiles());
  }
 }

 
 public void deleteUserBySSO(String sso) {
  dao.deleteBySSO(sso);
 }

 public List<User> findAllUsers() {
  return dao.findAllUsers();
 }

 public boolean isUserSSOUnique(Integer id, String sso) {
  User user = findBySSO(sso);
  return ( user == null || ((id != null) && (user.getId() == id)));
 }
 
}

Spring MVC Controller Setup

package com.websystique.springmvc.controller;

import java.util.List;
import java.util.Locale;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.SessionAttributes;

import com.websystique.springmvc.model.User;
import com.websystique.springmvc.model.UserProfile;
import com.websystique.springmvc.service.UserProfileService;
import com.websystique.springmvc.service.UserService;



@Controller
@RequestMapping("/")
@SessionAttributes("roles")
public class AppController {

 @Autowired
 UserService userService;
 
 @Autowired
 UserProfileService userProfileService;
 
 
 @Autowired
 MessageSource messageSource;

 /**
  * This method will list all existing users.
  */ @RequestMapping(value = { "/", "/list" }, method = RequestMethod.GET)
 public String listUsers(ModelMap model) {

  List<User> users = userService.findAllUsers();
  model.addAttribute("users", users);
  return "userslist";
 }

 /**
  * This method will provide the medium to add a new user.
  */ @RequestMapping(value = { "/newuser" }, method = RequestMethod.GET)
 public String newUser(ModelMap model) {
  User user = new User();
  model.addAttribute("user", user);
  model.addAttribute("edit", false);
  return "registration";
 }

 /**
  * This method will be called on form submission, handling POST request for
  * saving user in database. It also validates the user input
  */ @RequestMapping(value = { "/newuser" }, method = RequestMethod.POST)
 public String saveUser(@Valid User user, BindingResult result,
   ModelMap model) {

  if (result.hasErrors()) {
   return "registration";
  }

  /*
   * Preferred way to achieve uniqueness of field [sso] should be implementing custom @Unique annotation 
   * and applying it on field [sso] of Model class [User].
   * 
   * Below mentioned peace of code [if block] is to demonstrate that you can fill custom errors outside the validation
   * framework as well while still using internationalized messages.
   * 
   */  if(!userService.isUserSSOUnique(user.getId(), user.getSsoId())){
   FieldError ssoError =new FieldError("user","ssoId",messageSource.getMessage("non.unique.ssoId", new String[]{user.getSsoId()}, Locale.getDefault()));
      result.addError(ssoError);
   return "registration";
  }
  
  userService.saveUser(user);

  model.addAttribute("success", "User " + user.getFirstName() + " "+ user.getLastName() + " registered successfully");
  //return "success";
  return "registrationsuccess";
 }


 /**
  * This method will provide the medium to update an existing user.
  */ @RequestMapping(value = { "/edit-user-{ssoId}" }, method = RequestMethod.GET)
 public String editUser(@PathVariable String ssoId, ModelMap model) {
  User user = userService.findBySSO(ssoId);
  model.addAttribute("user", user);
  model.addAttribute("edit", true);
  return "registration";
 }
 
 /**
  * This method will be called on form submission, handling POST request for
  * updating user in database. It also validates the user input
  */ @RequestMapping(value = { "/edit-user-{ssoId}" }, method = RequestMethod.POST)
 public String updateUser(@Valid User user, BindingResult result,
   ModelMap model, @PathVariable String ssoId) {

  if (result.hasErrors()) {
   return "registration";
  }

  /*//Uncomment below 'if block' if you WANT TO ALLOW UPDATING SSO_ID in UI which is a unique key to a User.
  if(!userService.isUserSSOUnique(user.getId(), user.getSsoId())){
   FieldError ssoError =new FieldError("user","ssoId",messageSource.getMessage("non.unique.ssoId", new String[]{user.getSsoId()}, Locale.getDefault()));
      result.addError(ssoError);
   return "registration";
  }*/

  userService.updateUser(user);

  model.addAttribute("success", "User " + user.getFirstName() + " "+ user.getLastName() + " updated successfully");
  return "registrationsuccess";
 }

 
 /**
  * This method will delete an user by it's SSOID value.
  */ @RequestMapping(value = { "/delete-user-{ssoId}" }, method = RequestMethod.GET)
 public String deleteUser(@PathVariable String ssoId) {
  userService.deleteUserBySSO(ssoId);
  return "redirect:/list";
 }
 

 /**
  * This method will provide UserProfile list to views
  */ @ModelAttribute("roles")
 public List<UserProfile> initializeProfiles() {
  return userProfileService.findAll();
 }

}

messages.properties

NotEmpty.user.firstName=First name can not be blank.
NotEmpty.user.lastName=Last name can not be blank.
NotEmpty.user.email=Email can not be blank.
NotEmpty.user.password=Password can not be blank.
NotEmpty.user.ssoId=SSO ID can not be blank.
NotEmpty.user.userProfiles=At least one profile must be selected.
non.unique.ssoId=SSO ID {0} already exist. Please fill in different value.

Converter Setup

package com.websystique.springmvc.converter;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;

import com.websystique.springmvc.model.UserProfile;
import com.websystique.springmvc.service.UserProfileService;

/**
 * A converter class used in views to map id's to actual userProfile objects.
 */@Component
public class RoleToUserProfileConverter implements Converter<Object, UserProfile>{

 @Autowired
 UserProfileService userProfileService;

 /**
  * Gets UserProfile by Id
  * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
  */ public UserProfile convert(Object element) {
  Integer id = Integer.parseInt((String)element);
  UserProfile profile= userProfileService.findById(id);
  System.out.println("Profile : "+profile);
  return profile;
 }
 
}

App Configuration

package com.websystique.springmvc.configuration;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

import com.websystique.springmvc.converter.RoleToUserProfileConverter;


@Configuration
@Import(JpaConfiguration.class)
@EnableWebMvc
@ComponentScan(basePackages = "com.websystique.springmvc")
public class AppConfig extends WebMvcConfigurerAdapter{
 
 
 @Autowired
 RoleToUserProfileConverter roleToUserProfileConverter;
 

 /**
     * Configure ViewResolvers to deliver preferred views.
     */ @Override
 public void configureViewResolvers(ViewResolverRegistry registry) {

  InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
  viewResolver.setViewClass(JstlView.class);
  viewResolver.setPrefix("/WEB-INF/views/");
  viewResolver.setSuffix(".jsp");
  registry.viewResolver(viewResolver);
 }
 
 /**
     * Configure ResourceHandlers to serve static resources like CSS/ Javascript etc...
     */    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**").addResourceLocations("/static/");
    }
    
    /**
     * Configure Converter to be used.
     * In our example, we need a converter to convert string values[Roles] to UserProfiles in newUser.jsp
     */    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(roleToUserProfileConverter);
    }
 

    /**
     * Configure MessageSource to lookup any validation/error message in internationalized property files
     */    @Bean
 public MessageSource messageSource() {
     ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
     messageSource.setBasename("messages");
     return messageSource;
 }
    
    /**Optional. It's only required when handling '.' in @PathVariables which otherwise ignore everything after last '.' in @PathVaidables argument.
     * It's a known bug in Spring [https://jira.spring.io/browse/SPR-6164], still present in Spring 4.3.0.
     * This is a workaround for this issue.
     */    
    @Override
    public void configurePathMatch(PathMatchConfigurer matcher) {
        matcher.setUseRegisteredSuffixPatternMatch(true);
    }
    
}
package com.websystique.springmvc.configuration;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

 @Override
 protected Class<?>[] getRootConfigClasses() {
  return new Class[] { AppConfig.class };
 }
 
 @Override
 protected Class<?>[] getServletConfigClasses() {
  return null;
 }
 
 @Override
 protected String[] getServletMappings() {
  return new String[] { "/" };
 }

}

Step 11: Add Views/JSP’s

Note that we are using Bootstrap for styling in JSP.

userslist.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>

<head>
 <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
 <title>Users List</title>
 <link href="<c:url value='/static/css/bootstrap.css' />" rel="stylesheet"></link>
 <link href="<c:url value='/static/css/app.css' />" rel="stylesheet"></link>
</head>

<body>
 <div class="generic-container">
  <div class="panel panel-default">
     <!-- Default panel contents -->
     <div class="panel-heading"><span class="lead">List of Users </span></div>
   <table class="table table-hover">
       <thead>
          <tr>
            <th>Firstname</th>
            <th>Lastname</th>
            <th>Email</th>
            <th>SSO ID</th>
            <th width="100"></th>
            <th width="100"></th>
     </tr>
       </thead>
       <tbody>
    <c:forEach items="${users}" var="user">
     <tr>
      <td>${user.firstName}</td>
      <td>${user.lastName}</td>
      <td>${user.email}</td>
      <td>${user.ssoId}</td>
      <td><a href="<c:url value='/edit-user-${user.ssoId}' />" class="btn btn-success 

custom-width">edit</a></td>
      <td><a href="<c:url value='/delete-user-${user.ssoId}' />" class="btn btn-danger 

custom-width">delete</a></td>
     </tr>
    </c:forEach>
       </tbody>
      </table>
  </div>
   <div class="well">
    <a href="<c:url value='/newuser' />">Add New User</a>
   </div>
    </div>
</body>
</html>

registration.jsp

&lt;%@ page language=&quot;java&quot; contentType=&quot;text/html; charset=ISO-8859-1&quot; pageEncoding=&quot;ISO-8859-1&quot;%&gt;
&lt;%@ taglib prefix=&quot;form&quot; uri=&quot;http://www.springframework.org/tags/form&quot;%&gt;
&lt;%@ taglib prefix=&quot;c&quot; uri=&quot;http://java.sun.com/jsp/jstl/core&quot; %&gt;

&lt;html&gt;

&lt;head&gt;
 &lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=ISO-8859-1&quot;&gt;
 &lt;title&gt;User Registration Form&lt;/title&gt;
 &lt;link href=&quot;&lt;c:url value='/static/css/bootstrap.css' /&gt;&quot; rel=&quot;stylesheet&quot;&gt;&lt;/link&gt;
 &lt;link href=&quot;&lt;c:url value='/static/css/app.css' /&gt;&quot; rel=&quot;stylesheet&quot;&gt;&lt;/link&gt;
&lt;/head&gt;

&lt;body&gt;

  &lt;div class=&quot;generic-container&quot;&gt;
 &lt;div class=&quot;well lead&quot;&gt;User Registration Form&lt;/div&gt;
  &lt;form:form method=&quot;POST&quot; modelAttribute=&quot;user&quot; class=&quot;form-horizontal&quot;&gt;
  &lt;form:input type=&quot;hidden&quot; path=&quot;id&quot; id=&quot;id&quot;/&gt;
  
  &lt;div class=&quot;row&quot;&gt;
   &lt;div class=&quot;form-group col-md-12&quot;&gt;
    &lt;label class=&quot;col-md-3 control-lable&quot; >registrationsuccess.jsp</code>

&lt;%@ page language=&quot;java&quot; contentType=&quot;text/html; charset=ISO-8859-1&quot; pageEncoding=&quot;ISO-8859-1&quot;%&gt;
&lt;%@ taglib prefix=&quot;c&quot; uri=&quot;http://java.sun.com/jsp/jstl/core&quot; %&gt;


&lt;html&gt;
&lt;head&gt;
 &lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=ISO-8859-1&quot;&gt;
 &lt;title&gt;Registration Confirmation Page&lt;/title&gt;
 &lt;link href=&quot;&lt;c:url value='/static/css/bootstrap.css' /&gt;&quot; rel=&quot;stylesheet&quot;&gt;&lt;/link&gt;
 &lt;link href=&quot;&lt;c:url value='/static/css/app.css' /&gt;&quot; rel=&quot;stylesheet&quot;&gt;&lt;/link&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div class=&quot;generic-container&quot;&gt;
 &lt;div class=&quot;alert alert-success lead&quot;&gt;
     ${success}
 &lt;/div&gt;
 
 &lt;span class=&quot;well floatRight&quot;&gt;
  Go to &lt;a href=&quot;&lt;c:url value='/list' /&gt;&quot;&gt;Users List&lt;/a&gt;
 &lt;/span&gt;
&lt;/div&gt;
&lt;/body&gt;

&lt;/html&gt;

Step 12: Build, deploy and Run Application

Now build the war (either by eclipse as was mentioned in previous tutorials) or via maven command line( mvn clean install). Deploy the war to a Servlet 3.0 container . Since here i am using Tomcat, i will simply put this war file into tomcat webapps folder and click on start.bat inside tomcat/bin directory.

Open browser and browse at http://localhost:8080/SpringMVCJPA2HibernateManyToManyCRUDExample/

Click on ‘Add New User’

Fill in details

Submit.

Click on ‘Users List’ link.

Check the database at this moment.

Add more users.

Click Edit button for User Kenny. Change Roles.

Verify the database.

Now go back to list and click on DELETE for user kenny. It should be history.

Finally check the database at this moment :

That’s it. As you saw, pure JPA setup is straight forward. Easy though, I must admit that i found few implementation specific things [like Criteria in Hibernate] much more simple to work with compare to the one from standard JPA. Being standard JPA does gives you an upper hand if you have to switch the provider one day.

Download Source Code


References

View Comments

  • Hi my loved one! I wish to say that this post is amazing, nice written and include approximately all vital infos. I'd like to peer more posts like this.

  • The Elitepipe Plastic Factory in Iraq serves as a catalyst for infrastructure development, providing the market with superior HDPE, uPVC pipes, and fittings that contribute to the growth and success of various sectors. Elitepipe Plastic Factory

  • Hey Team, is there any way to configure auto creation of tables in this example ??

  • I've built war file, but as usual in the tomcat webapp directory and run, page shows such an error:

    HTTP Status 500 - Request processing failed; nested exception is org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction; nested exception is javax.persistence.PersistenceException: org.hibernate.exception.JDBCConnectionException: Could not open connection

    • As I understand now I didn't create and populate mysql database with any data. Maybe that's the case.

        • i've got error message javax.validation.UnexpectedTypeException: HV000030: No validator could be found for type: java.lang.Integer.
          when I save object Book, may you explain me ? thank you, sorry my english is not good.
          this is Table :
          @Entity
          @Table(name = "book")
          public class BookEntity implements Serializable {

          @Id
          @GeneratedValue(strategy = GenerationType.IDENTITY)
          private Integer id;

          @NotEmpty
          @Column(name = "sso_id", unique = true, nullable = false)
          private String ssoID;

          @NotEmpty
          @Column(name = "name", nullable = false)
          private String name;

          @NotEmpty
          @Column(name = "author", nullable = false)
          private String author;

          @NotEmpty
          @Column(name = "description", nullable = false)
          private String description;

          @NotEmpty
          @Column(name = "image", nullable = false)
          private String image;

          @NotEmpty
          @Column(name = "numberOfPage", nullable = false)
          private int numberOfPage;

          @NotEmpty
          @Column(name = "price", nullable = false)
          private int price;

          @NotEmpty
          @Column(name = "quantity", nullable = false)
          private int quantity;

          @NotEmpty
          @ManyToMany(fetch = FetchType.LAZY)
          @JoinTable(name = "book_cat",
          joinColumns = {@JoinColumn(name = "book_id")},
          inverseJoinColumns = {@JoinColumn(name = "cat_id")})
          private Set categoryEntitySet = new HashSet();
          ...
          }

          • You are trying to use @NotEmpty on integer, which is not supported as per documentation. For int, you may want to use javax.validation.constraints.NotNull.
            Additionally, you may want to use Integer instead of int.

      • Hi. Maybe, I didn't express myself clearly enough. I have a problem where i input text in forms. Text is saved in database as "йцукен".

        • I found where was the trouble! The CharacterEncodingFilter must be started before SecurityFilter. I moved it to security configuration:

          @Override
          protected void configure(HttpSecurity http) throws Exception {
          CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
          encodingFilter.setEncoding("UTF-8");
          encodingFilter.setForceEncoding(true);

          http.addFilterBefore(encodingFilter, CsrfFilter.class);

          Now works perfect!

        • Hi. I have the same problem. It's happens when typing in input fields russian symbols and then saving data in MySQL by POST method. All .java, .jsp files encoding and MySQL configured on UTF-8. If added new data in mysql throught sql query - all work fine.

          • Hi. Thanks for the answer, but the first thing I did was just that. Without changes. I think need to use CharacterEncodingFilter, but my method don't work.

            I added this code to AppInitializer class:

            @Override
            protected Filter[] getServletFilters() {
            return new Filter[]{new CharacterEncodingFilter()};
            }

          • Yea, it's was good idea. With you way i solved problem. But with some corrected.
            The first, you need adding this method to class AppInitializer. Not in class AppConfig.
            The second, you need insert encoding name into method constructor CharacterEncodingFilter().

            Example:
            ................
            import org.springframework.web.filter.CharacterEncodingFilter;
            import javax.servlet.Filter;
            ...............
            public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
            ...............
            @Override
            protected Filter[] getServletFilters() {
            return new Filter[]{new CharacterEncodingFilter("UTF-8")};
            }
            ...............
            }

          • Hmm, i dont know then. For me - work. Make sure that all project files have UTF-8 encoding(.java, ,jsp). As well check encoding in your database, tables. Should be everywhere UTF-8.

          • The problem is that when you add text using Ajax everything works well and in database a text appears as it should, but when I use a form that is generated Spring, it turns out that the Problem of which I spoke.

Share
Published by

Recent Posts

Spring Boot + AngularJS + Spring Data + JPA CRUD App Example

In this post we will be developing a full-blown CRUD application using Spring Boot, AngularJS, Spring Data, JPA/Hibernate and MySQL,…

7 years ago

Spring Boot Rest API Example

Spring Boot complements Spring REST support by providing default dependencies/converters out of the box. Writing RESTful services in Spring Boot…

7 years ago

Spring Boot WAR deployment example

Being able to start the application as standalone jar is great, but sometimes it might not be possible to run…

7 years ago

Spring Boot Introduction + hello world example

Spring framework has taken the software development industry by storm. Dependency Injection, rock solid MVC framework, Transaction management, messaging support,…

7 years ago

Secure Spring REST API using OAuth2

Let's secure our Spring REST API using OAuth2 this time, a simple guide showing what is required to secure a…

8 years ago

AngularJS+Spring Security using Basic Authentication

This post shows how an AngularJS application can consume a REST API which is secured with Basic authentication using Spring…

8 years ago