This post shows uploading files into Database using Spring MVC 4, Hibernate & MySQL Database. Spring MVC File upload database example, download and delete from database using Hibernate+MySQL. Let’s get going.
Main highlights of the posts are:
- Prepare database side setup [create required tables]
- Prepare Model Classes, DAO and Service layer [will be used by controller to handle file upload/download/delete operation]
- Prepate Hibernate configuration
- Prepare Spring Configuration for File Upload/download and delete.
- And finally run application to upload files, download files and delete files.
You may prefer to directly jump on the specific sections you are interested in as it’s a long post with complete example.
- Spring Boot+AngularJS+Spring Data+Hibernate+MySQL CRUD App
- Spring Boot REST API Tutorial
- Secure Spring REST API using OAuth2
- Spring Boot WAR deployment example
- Spring Boot Introduction + Hello World Example
- AngularJS+Spring Security using Basic Authentication
- Secure Spring REST API using Basic Authentication
- Spring 4 Caching Annotations Tutorial
- Spring 4 Cache Tutorial with EhCache
- Spring 4 MVC+JPA2+Hibernate Many-to-many Example
- Spring 4 Email Template Library Example
- Spring 4 Email With Attachment Tutorial
- Spring 4 Email Integration Tutorial
- Spring MVC 4+JMS+ActiveMQ Integration Example
- Spring 4+JMS+ActiveMQ @JmsLister @EnableJms Example
- Spring 4+JMS+ActiveMQ Integration Example
- Spring MVC 4+Spring Security 4 + Hibernate Integration Example
- Spring MVC 4 File Upload Example using Commons fileupload
- Spring MVC 4+AngularJS Server communication example : CRUD application using ngResource $resource service
- Spring MVC 4+AngularJS Routing with UI-Router Example
- Spring MVC 4+AngularJS Routing with ngRoute Example
- Spring MVC 4 File Upload Example using Servlet 3 MultiPartConfigElement
- Spring MVC 4+AngularJS Example
- Spring MVC 4 File Download Example
- Spring MVC 4+Hibernate 4 Many-to-many JSP Example
- Spring MVC 4+Hibernate 4+MySQL+Maven integration example
- Spring MVC 4+Hibernate 4+MySQL+Maven integration + Testing example using annotations
Following technologies being used:
- Spring 4.2.0.RELEASE
- Hibernate 4.3.10.Final
- MySQL Server 5.6
- validation-api 1.1.0.Final
- hibernate-validator 5.1.3.Final
- Bootstrap v3.3.2
- Maven 3
- JDK 1.7
- Tomcat 8.0.21
- Eclipse JUNO Service Release 2
Let’s begin.
Project directory structure
Following will be the final project structure:
Step 2: Declare Dependencies in pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.websystique.springmvc</groupId> <artifactId>Spring4MVCFileUploadDownloadWithHibernate</artifactId> <packaging>war</packaging> <version>1.0.0</version> <name>Spring4MVCFileUploadDownloadWithHibernate Maven Webapp</name> <url>http://maven.apache.org</url> <properties> <springframework.version>4.2.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> <!-- 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>Spring4MVCFileUploadDownloadWithHibernate</warName> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </pluginManagement> <finalName>Spring4MVCFileUploadDownloadWithHibernate</finalName> </build> </project>
Create Schema
We will take a trivial example of User and documents relationship. A User can have several documents, it’s a OneToMany Relationship between User and his Documents.
create table APP_USER ( id BIGINT NOT NULL AUTO_INCREMENT, sso_id VARCHAR(30) 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_DOCUMENT( id BIGINT NOT NULL AUTO_INCREMENT, user_id BIGINT NOT NULL, name VARCHAR(100) NOT NULL, description VARCHAR(255) , type VARCHAR(100) NOT NULL, content longblob NOT NULL, PRIMARY KEY (id), CONSTRAINT document_user FOREIGN KEY (user_id) REFERENCES APP_USER (id) ON UPDATE CASCADE ON DELETE CASCADE );
Highlight of above schema is content longblob
. We will store the files into binary format in this column.
Create Model Classes
Let’s create Model classes that maps to above mentioned tables.
package com.websystique.springmvc.model; import javax.persistence.Basic; 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.Lob; import javax.persistence.ManyToOne; import javax.persistence.Table; @Entity @Table(name="USER_DOCUMENT") public class UserDocument { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Integer id; @Column(name="name", length=100, nullable=false) private String name; @Column(name="description", length=255) private String description; @Column(name="type", length=100, nullable=false) private String type; @Lob @Basic(fetch = FetchType.LAZY) @Column(name="content", nullable=false) private byte[] content; @ManyToOne(optional = false) @JoinColumn(name = "USER_ID") private User user; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getType() { return type; } public void setType(String type) { this.type = type; } public byte[] getContent() { return content; } public void setContent(byte[] content) { this.content = content; } public User getUser() { return user; } public void setUser(User user) { this.user = user; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((id == null) ? 0 : id.hashCode()); result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (!(obj instanceof UserDocument)) return false; UserDocument other = (UserDocument) obj; if (id == null) { if (other.id != null) return false; } else if (!id.equals(other.id)) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } @Override public String toString() { return "UserDocument [id=" + id + ", name=" + name + ", description=" + description + ", type=" + type + "]"; } }
Main feature of this Entity class is the content property
@Lob @Basic(fetch = FetchType.LAZY) @Column(name="content", nullable=false) private byte[] content;
We have chosen a byte[] to store the content of file. @Lob
Specifies that a this persistent property should be persisted as a large object to a database-supported large object type which in our case is longblob
. @Basic
annotation is an optional annotation, serving here as placeholder to instruct hibernate to lazy load the binary content.
Below is the corresponding User class.
package com.websystique.springmvc.model; import java.util.HashSet; import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.Table; import org.hibernate.validator.constraints.NotEmpty; @Entity @Table(name="APP_USER") public class User { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Integer id; @NotEmpty @Column(name="SSO_ID", unique=true, nullable=false) private String ssoId; @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; @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) private Set<UserDocument> userDocuments = new HashSet<UserDocument>(); 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 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<UserDocument> getUserDocuments() { return userDocuments; } public void setUserDocuments(Set<UserDocument> userDocuments) { this.userDocuments = userDocuments; } @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 + ", firstName=" + firstName + ", lastName=" + lastName + ", email=" + email + "]"; } }
Nothing special,just a trivial one-to-many mapping with UserDocument. A User can have several documents.
Create DAO layer
package com.websystique.springmvc.dao; import java.util.List; import com.websystique.springmvc.model.UserDocument; public interface UserDocumentDao { List<UserDocument> findAll(); UserDocument findById(int id); void save(UserDocument document); List<UserDocument> findAllByUserId(int userId); void deleteById(int id); }
package com.websystique.springmvc.dao; import java.util.List; import org.hibernate.Criteria; import org.hibernate.criterion.Restrictions; import org.springframework.stereotype.Repository; import com.websystique.springmvc.model.UserDocument; @Repository("userDocumentDao") public class UserDocumentDaoImpl extends AbstractDao<Integer, UserDocument> implements UserDocumentDao{ @SuppressWarnings("unchecked") public List<UserDocument> findAll() { Criteria crit = createEntityCriteria(); return (List<UserDocument>)crit.list(); } public void save(UserDocument document) { persist(document); } public UserDocument findById(int id) { return getByKey(id); } @SuppressWarnings("unchecked") public List<UserDocument> findAllByUserId(int userId){ Criteria crit = createEntityCriteria(); Criteria userCriteria = crit.createCriteria("user"); userCriteria.add(Restrictions.eq("id", userId)); return (List<UserDocument>)crit.list(); } public void deleteById(int id) { UserDocument document = getByKey(id); delete(document); } }
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.List; import org.hibernate.Criteria; import org.hibernate.criterion.Order; import org.hibernate.criterion.Restrictions; 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); return user; } public User findBySSO(String sso) { System.out.println("SSO : "+sso); Criteria crit = createEntityCriteria(); crit.add(Restrictions.eq("ssoId", sso)); User user = (User)crit.uniqueResult(); return user; } @SuppressWarnings("unchecked") public List<User> findAllUsers() { Criteria criteria = createEntityCriteria().addOrder(Order.asc("firstName")); criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);//To avoid duplicates. List<User> users = (List<User>) criteria.list(); return users; } public void save(User user) { persist(user); } public void deleteBySSO(String sso) { Criteria crit = createEntityCriteria(); crit.add(Restrictions.eq("ssoId", sso)); User user = (User)crit.uniqueResult(); delete(user); } }
package com.websystique.springmvc.dao; import java.io.Serializable; import java.lang.reflect.ParameterizedType; import org.hibernate.Criteria; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; 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]; } @Autowired private SessionFactory sessionFactory; protected Session getSession(){ return sessionFactory.getCurrentSession(); } @SuppressWarnings("unchecked") public T getByKey(PK key) { return (T) getSession().get(persistentClass, key); } public void persist(T entity) { getSession().persist(entity); } public void delete(T entity) { getSession().delete(entity); } protected Criteria createEntityCriteria(){ return getSession().createCriteria(persistentClass); } }
Create Service Layer
package com.websystique.springmvc.service; import java.util.List; import com.websystique.springmvc.model.UserDocument; public interface UserDocumentService { UserDocument findById(int id); List<UserDocument> findAll(); List<UserDocument> findAllByUserId(int id); void saveDocument(UserDocument document); void deleteById(int id); }
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.UserDocumentDao; import com.websystique.springmvc.model.UserDocument; @Service("userDocumentService") @Transactional public class UserDocumentServiceImpl implements UserDocumentService{ @Autowired UserDocumentDao dao; public UserDocument findById(int id) { return dao.findById(id); } public List<UserDocument> findAll() { return dao.findAll(); } public List<UserDocument> findAllByUserId(int userId) { return dao.findAllByUserId(userId); } public void saveDocument(UserDocument document){ dao.save(document); } public void deleteById(int id){ dao.deleteById(id); } }
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.setFirstName(user.getFirstName()); entity.setLastName(user.getLastName()); entity.setEmail(user.getEmail()); entity.setUserDocuments(user.getUserDocuments()); } } 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))); } }
Create Hibernate Configuration Class
package com.websystique.springmvc.configuration; import java.util.Properties; import javax.sql.DataSource; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; 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.hibernate4.HibernateTransactionManager; import org.springframework.orm.hibernate4.LocalSessionFactoryBean; import org.springframework.transaction.annotation.EnableTransactionManagement; @Configuration @EnableTransactionManagement @ComponentScan({ "com.websystique.springmvc.configuration" }) @PropertySource(value = { "classpath:application.properties" }) public class HibernateConfiguration { @Autowired private Environment environment; @Bean public LocalSessionFactoryBean sessionFactory() { LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean(); sessionFactory.setDataSource(dataSource()); sessionFactory.setPackagesToScan(new String[] { "com.websystique.springmvc.model" }); sessionFactory.setHibernateProperties(hibernateProperties()); return sessionFactory; } @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; } private Properties hibernateProperties() { Properties properties = new Properties(); properties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect")); 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 HibernateTransactionManager transactionManager(SessionFactory s) { HibernateTransactionManager txManager = new HibernateTransactionManager(); txManager.setSessionFactory(s); return txManager; } }
This class uses following 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.MySQLDialect hibernate.show_sql = true hibernate.format_sql = true
Prepare Configuration for File Upload
Spring provides several possibilities to upload a file. We will use one of possible way, which is Programmatic registration of Servlet 3.0 specific javax.servlet.MultipartConfigElement
with SpringMVC DispatcherServlet. This registration provides opportunity to set specific properties like maximum file size, request size, location and threshold after which file will be stored temporarily on disk during upload operation.
Additionally, we need to add StandardServletMultipartResolver
Bean to our Spring Configuration. It’s a standard implementation of the MultipartResolver
interface, based on the Servlet 3.0 javax.servlet.http.Part
API.
package com.websystique.springmvc.configuration; import javax.servlet.MultipartConfigElement; import javax.servlet.ServletRegistration; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; public class HelloWorldInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[] { HelloWorldConfiguration.class }; } @Override protected Class<?>[] getServletConfigClasses() { return null; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } @Override protected void customizeRegistration(ServletRegistration.Dynamic registration) { registration.setMultipartConfig(getMultipartConfigElement()); } private MultipartConfigElement getMultipartConfigElement(){ MultipartConfigElement multipartConfigElement = new MultipartConfigElement(LOCATION, MAX_FILE_SIZE, MAX_REQUEST_SIZE, FILE_SIZE_THRESHOLD); return multipartConfigElement; } /*Set these variables for your project needs*/ private static final String LOCATION = "C:/mytemp/"; private static final long MAX_FILE_SIZE = 1024 * 1024 * 25;//25MB private static final long MAX_REQUEST_SIZE = 1024 * 1024 * 30;//30MB private static final int FILE_SIZE_THRESHOLD = 0; }
Notice how we have overridden function customizeRegistration in order to register the required MultiPartConfigElement to DispatcherServlet. The next step to activate multipart support is to register a Bean of type StandardServletMultipartResolver
as shown below in configuration class.
This article is showing only one way of file upload with Spring. You may also like Spring MVC 4 File Upload Example using Commons fileupload and Spring MVC 4 File Upload Example using Servlet 3 MultiPartConfigElement to learn more details and other possibilities to upload a file with Spring.
Create Spring Configuration Class
package com.websystique.springmvc.configuration; 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.support.ResourceBundleMessageSource; import org.springframework.web.multipart.support.StandardServletMultipartResolver; 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; @Configuration @EnableWebMvc @ComponentScan(basePackages = "com.websystique.springmvc") public class HelloWorldConfiguration extends WebMvcConfigurerAdapter{ @Bean(name="multipartResolver") public StandardServletMultipartResolver resolver(){ return new StandardServletMultipartResolver(); } /** * 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 MessageSource to lookup any validation/error message in internationalized property files */ @Bean public MessageSource messageSource() { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); messageSource.setBasename("messages"); return messageSource; } }
Create Wrapper class for File
Spring provides org.springframework.web.multipart.MultipartFile
which is a representation of an uploaded file received in a multipart request. It provides handy methods like getName(), getContentType(), getBytes(), getInputStream() etc.. which make life bit easier while retrieving information about file being uploaded.
Let’s write a wrapper class to further simply it’s usage in our application
package com.websystique.springmvc.model; import org.springframework.web.multipart.MultipartFile; public class FileBucket { MultipartFile file; String description; public MultipartFile getFile() { return file; } public void setFile(MultipartFile file) { this.file = file; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } }
Create Spring MVC Controller Class
Following is a trivial controller class. Methods we are interested in are uploadDocument, downloadDocument & deleteDocument. Rest of the methods are simple methods to save/update/delete a user and basic navigation.
package com.websystique.springmvc.controller; import java.io.IOException; import java.util.List; import java.util.Locale; import javax.servlet.http.HttpServletResponse; 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.util.FileCopyUtils; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; 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.multipart.MultipartFile; import com.websystique.springmvc.model.FileBucket; import com.websystique.springmvc.model.User; import com.websystique.springmvc.model.UserDocument; import com.websystique.springmvc.service.UserDocumentService; import com.websystique.springmvc.service.UserService; import com.websystique.springmvc.util.FileValidator; @Controller @RequestMapping("/") public class AppController { @Autowired UserService userService; @Autowired UserDocumentService userDocumentService; @Autowired MessageSource messageSource; @Autowired FileValidator fileValidator; @InitBinder("fileBucket") protected void initBinder(WebDataBinder binder) { binder.setValidator(fileValidator); } /** * 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("user", 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"; } 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"; } @RequestMapping(value = { "/add-document-{userId}" }, method = RequestMethod.GET) public String addDocuments(@PathVariable int userId, ModelMap model) { User user = userService.findById(userId); model.addAttribute("user", user); FileBucket fileModel = new FileBucket(); model.addAttribute("fileBucket", fileModel); List<UserDocument> documents = userDocumentService.findAllByUserId(userId); model.addAttribute("documents", documents); return "managedocuments"; } @RequestMapping(value = { "/download-document-{userId}-{docId}" }, method = RequestMethod.GET) public String downloadDocument(@PathVariable int userId, @PathVariable int docId, HttpServletResponse response) throws IOException { UserDocument document = userDocumentService.findById(docId); response.setContentType(document.getType()); response.setContentLength(document.getContent().length); response.setHeader("Content-Disposition","attachment; filename=\"" + document.getName() +"\""); FileCopyUtils.copy(document.getContent(), response.getOutputStream()); return "redirect:/add-document-"+userId; } @RequestMapping(value = { "/delete-document-{userId}-{docId}" }, method = RequestMethod.GET) public String deleteDocument(@PathVariable int userId, @PathVariable int docId) { userDocumentService.deleteById(docId); return "redirect:/add-document-"+userId; } @RequestMapping(value = { "/add-document-{userId}" }, method = RequestMethod.POST) public String uploadDocument(@Valid FileBucket fileBucket, BindingResult result, ModelMap model, @PathVariable int userId) throws IOException{ if (result.hasErrors()) { System.out.println("validation errors"); User user = userService.findById(userId); model.addAttribute("user", user); List<UserDocument> documents = userDocumentService.findAllByUserId(userId); model.addAttribute("documents", documents); return "managedocuments"; } else { System.out.println("Fetching file"); User user = userService.findById(userId); model.addAttribute("user", user); saveDocument(fileBucket, user); return "redirect:/add-document-"+userId; } } private void saveDocument(FileBucket fileBucket, User user) throws IOException{ UserDocument document = new UserDocument(); MultipartFile multipartFile = fileBucket.getFile(); document.setName(multipartFile.getOriginalFilename()); document.setDescription(fileBucket.getDescription()); document.setType(multipartFile.getContentType()); document.setContent(multipartFile.getBytes()); document.setUser(user); userDocumentService.saveDocument(document); } }
Create Validator
Create a simple validator to validate file input.
package com.websystique.springmvc.util; import org.springframework.stereotype.Component; import org.springframework.validation.Errors; import org.springframework.validation.Validator; import com.websystique.springmvc.model.FileBucket; @Component public class FileValidator implements Validator { public boolean supports(Class<?> clazz) { return FileBucket.class.isAssignableFrom(clazz); } public void validate(Object obj, Errors errors) { FileBucket file = (FileBucket) obj; if(file.getFile()!=null){ if (file.getFile().getSize() == 0) { errors.rejectValue("file", "missing.file"); } } } }
Following is the properties file messages.properties
, used for entire application.
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.ssoId=SSO ID can not be blank. non.unique.ssoId=SSO ID {0} already exist. Please fill in different value. missing.file= Please select a file.
Create Views
There are several views in this application. What we are most interested in is the one with file uploads/downloads. It’s shown below
managedocuments.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" %> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Upload/Download/Delete Documents</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 Documents </span></div> <div class="tablecontainer"> <table class="table table-hover"> <thead> <tr> <th>No.</th> <th>File Name</th> <th>Type</th> <th>Description</th> <th width="100"></th> <th width="100"></th> </tr> </thead> <tbody> <c:forEach items="${documents}" var="doc" varStatus="counter"> <tr> <td>${counter.index + 1}</td> <td>${doc.name}</td> <td>${doc.type}</td> <td>${doc.description}</td> <td><a href="<c:url value='/download-document-${user.id}-${doc.id}' />" class="btn btn-success custom-width">download</a></td> <td><a href="<c:url value='/delete-document-${user.id}-${doc.id}' />" class="btn btn-danger custom-width">delete</a></td> </tr> </c:forEach> </tbody> </table> </div> </div> <div class="panel panel-default"> <div class="panel-heading"><span class="lead">Upload New Document</span></div> <div class="uploadcontainer"> <form:form method="POST" modelAttribute="fileBucket" enctype="multipart/form-data" class="form-horizontal"> <div class="row"> <div class="form-group col-md-12"> <label class="col-md-3 control-lable" for="file">Upload a document</label> <div class="col-md-7"> <form:input type="file" path="file" id="file" class="form-control input-sm"/> <div class="has-error"> <form:errors path="file" class="help-inline"/> </div> </div> </div> </div> <div class="row"> <div class="form-group col-md-12"> <label class="col-md-3 control-lable" for="description">Description</label> <div class="col-md-7"> <form:input type="text" path="description" id="description" class="form-control input-sm"/> </div> </div> </div> <div class="row"> <div class="form-actions floatRight"> <input type="submit" value="Upload" class="btn btn-primary btn-sm"> </div> </div> </form:form> </div> </div> <div class="well"> Go to <a href="<c:url value='/list' />">Users List</a> </div> </div> </body> </html>
Main points to take from here is enctype="multipart/form-data"
and input type="file"
for file input. Rest is usual Spring MVC stuff.
Other views in this application are shown below [feel free to skip them].
registration.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> <%@ 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>User Registration Form</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="well lead">User Registration Form</div> <form:form method="POST" modelAttribute="user" class="form-horizontal"> <form:input type="hidden" path="id" id="id"/> <div class="row"> <div class="form-group col-md-12"> <label class="col-md-3 control-lable" for="firstName">First Name</label> <div class="col-md-7"> <form:input type="text" path="firstName" id="firstName" class="form-control input-sm"/> <div class="has-error"> <form:errors path="firstName" class="help-inline"/> </div> </div> </div> </div> <div class="row"> <div class="form-group col-md-12"> <label class="col-md-3 control-lable" for="lastName">Last Name</label> <div class="col-md-7"> <form:input type="text" path="lastName" id="lastName" class="form-control input-sm" /> <div class="has-error"> <form:errors path="lastName" class="help-inline"/> </div> </div> </div> </div> <div class="row"> <div class="form-group col-md-12"> <label class="col-md-3 control-lable" for="ssoId">SSO ID</label> <div class="col-md-7"> <c:choose> <c:when test="${edit}"> <form:input type="text" path="ssoId" id="ssoId" class="form-control input-sm" disabled="true"/> </c:when> <c:otherwise> <form:input type="text" path="ssoId" id="ssoId" class="form-control input-sm" /> <div class="has-error"> <form:errors path="ssoId" class="help-inline"/> </div> </c:otherwise> </c:choose> </div> </div> </div> <div class="row"> <div class="form-group col-md-12"> <label class="col-md-3 control-lable" for="email">Email</label> <div class="col-md-7"> <form:input type="text" path="email" id="email" class="form-control input-sm" /> <div class="has-error"> <form:errors path="email" class="help-inline"/> </div> </div> </div> </div> <div class="row"> <div class="form-actions floatRight"> <c:choose> <c:when test="${edit}"> <input type="submit" value="Update" class="btn btn-primary btn-sm"/> or <a href="<c:url value='/list' />">Cancel</a> </c:when> <c:otherwise> <input type="submit" value="Register" class="btn btn-primary btn-sm"/> or <a href="<c:url value='/list' />">Cancel</a> </c:otherwise> </c:choose> </div> </div> <c:if test="${edit}"> <span class="well pull-left"> <a href="<c:url value='/add-document-${user.id}' />">Click here to upload/manage your documents</a> </span> </c:if> </form:form> </div> </body> </html>
registrationsuccess.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>Registration Confirmation Page</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="alert alert-success lead"> ${success} </div> <span class="well pull-left"> <a href="<c:url value='/add-document-${user.id}' />">Click here to upload/manage your documents</a> </span> <span class="well pull-right"> Go to <a href="<c:url value='/list' />">Users List</a> </span> </div> </body> </html>
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> <div class="tablecontainer"> <table class="table table-hover"> <thead> <tr> <th>First Name</th> <th>Last Name</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> <div class="well"> <a href="<c:url value='/newuser' />">Add New User</a> </div> </div> </body> </html>
Error Handling
During file upload process, you may get problem like “Packet for query is too large”.
To overcome this issue, you need to update the ‘max_allowed_packet’ value in your Mysql configuration file.
- On Windows you may find it in MySQL Server 5.6/my.ini
- On Linux you may find it in etc/my.cnf
By default, it’s set to 4M. You can set it based on your needs.
max_allowed_packet=256M
Don’t forget to restart MySQL Server after update.
You can check the updated value by executing this query
>show variables like ‘max%’ ;
Build, Deploy & 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/Spring4MVCFileUploadDownloadWithHibernate
Add a user.
Fill in details and register. You should see confirmation.
Click on upload/manage link.
Select a file, provide a description.
Click on upload.
Upload more files.
Verify database.
You can see that user_documents table contains 3 rows with content type shown as ‘BLOB’;.
Now click on download buttons of some of above rows. File should be downloaded right away.
Let’s click on delete for few rows. Those user_documents should be deleted from database as well.
Verify database.
Verify validation. Click on Upload without selecting a file. You should see validation error.
Download Source Code
References
If you like tutorials on this site, why not take a step further and connect me on Facebook , Google Plus & Twitter as well? I would love to hear your thoughts on these articles, it will help improve further our learning process.