Spring MVC 3 and Hibernate Tutorials Series
Spring - Hibernate: Many-To-One Association - Explicitly Specify Join Table, Cascade, and Fetch
Spring - Hibernate: One-To-Many Association - Explicitly Specify Join Table, Cascade, and Fetch
Spring - Hibernate: Many-To-One Association
Spring - Hibernate: One-To-Many Association
Spring MVC 3, Hibernate Annotations, MySQL Integration Tutorial
Spring MVC 3, Hibernate Annotations, HSQLDB Integration Tutorial
What is Hibernate?
Hibernate is an object-relational mapping (ORM) library for the Java language, providing a framework for mapping an object-oriented domain model to a traditional relational database. Hibernate solves object-relational impedance mismatch problems by replacing direct persistence-related database accesses with high-level object handling functions
Source: http://en.wikipedia.org/wiki/Hibernate_(Java)
What is MySQL?
The MySQL Database powers the most demanding Web, E-commerce and Online Transaction Processing (OLTP) applications. It is a fully integrated transaction-safe, ACID compliant database with full commit, rollback, crash recovery and row level locking capabilities. MySQL delivers the ease of use, scalability, and performance that has made MySQL the world's most popular open source database. Some of the world's most trafficked websites like Facebook, Google, ticketmaster, and eBay rely on MySQL for their business critical applications.Let's preview first the final structure of our project.
Source: http://www.mysql.com/products/enterprise/database/
And here's how our application would look like:
It's a simple, crude CRUD system (pun intended).
We start by defining our domain object Person
Person
package org.krams.tutorial.domain; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; /** * For a complete reference see * * Hibernate Annotations Communit Documentations */ @Entity @Table(name = "PERSON") public class Person implements Serializable { private static final long serialVersionUID = -5527566248002296042L; @Id @Column(name = "ID") @GeneratedValue private Integer id; @Column(name = "FIRST_NAME") private String firstName; @Column(name = "LAST_NAME") private String lastName; @Column(name = "MONEY") private Double money; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } 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 Double getMoney() { return money; } public void setMoney(Double money) { this.money = money; } }Person is a simple POJO containing four private fields:
id firstName lastName moneyEach of these fields have been annotated with @Column and assigned with corresponding names.
ID FIRST_NAME LAST_NAME MONEYThese column names are database column names. You don't deal with them. Instead, Hibernate is the one responsible for managing your database. However, you are responsible for declaring the column names in the POJO. You don't declare them in your database. Remember your database doesn't exist yet.
The POJO has been annotated to map to a database table. If you look at the declaration of the Person class we see the annotation @Table and the name of the actual table:
@Entity @Table(name = "PERSON") public class Person implements SerializableNotice the annotation @Entity before the @Table. This tells Hibernate that this POJO should be mapped to a database table.
Since we will manipulate a list of persons, let's declare a service that manipulates a list of Persons.
PersonService
package org.krams.tutorial.service; import java.util.List; import javax.annotation.Resource; import org.apache.log4j.Logger; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.krams.tutorial.domain.Person; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * Service for processing Persons * */ @Service("personService") @Transactional public class PersonService { protected static Logger logger = Logger.getLogger("service"); @Resource(name="sessionFactory") private SessionFactory sessionFactory; /** * Retrieves all persons * * @return a list of persons */ public ListWe've declared a simple CRUD system with the following methods:getAll() { logger.debug("Retrieving all persons"); // Retrieve session from Hibernate Session session = sessionFactory.getCurrentSession(); // Create a Hibernate query (HQL) Query query = session.createQuery("FROM Person"); // Retrieve all return query.list(); } /** * Retrieves a single person */ public Person get( Integer id ) { // Retrieve session from Hibernate Session session = sessionFactory.getCurrentSession(); // Retrieve existing person first Person person = (Person) session.get(Person.class, id); return person; } /** * Adds a new person */ public void add(Person person) { logger.debug("Adding new person"); // Retrieve session from Hibernate Session session = sessionFactory.getCurrentSession(); // Save session.save(person); } /** * Deletes an existing person * @param id the id of the existing person */ public void delete(Integer id) { logger.debug("Deleting existing person"); // Retrieve session from Hibernate Session session = sessionFactory.getCurrentSession(); // Retrieve existing person first Person person = (Person) session.get(Person.class, id); // Delete session.delete(person); } /** * Edits an existing person */ public void edit(Person person) { logger.debug("Editing existing person"); // Retrieve session from Hibernate Session session = sessionFactory.getCurrentSession(); // Retrieve existing person via id Person existingPerson = (Person) session.get(Person.class, person.getId()); // Assign updated values to this person existingPerson.setFirstName(person.getFirstName()); existingPerson.setLastName(existingPerson.getLastName()); existingPerson.setMoney(existingPerson.getMoney()); // Save updates session.save(existingPerson); } }
getAll() add() delete() edit()In each method we retrieve the session:
Session session = sessionFactory.getCurrentSession();This is similar to retrieving a connection from the database so that we can do our work. The Session object provides numerous methods for persisting objects. For this tutorial, we use the following Session methods:
session.createQuery() session.save() session.delete()We're done with the domain and the service layer. Let's move to the Spring controller.
MainController
package org.krams.tutorial.controller; import java.util.List; import javax.annotation.Resource; import org.apache.log4j.Logger; import org.krams.tutorial.domain.Person; import org.krams.tutorial.service.PersonService; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; /** * Handles and retrieves person request */ @Controller @RequestMapping("/main") public class MainController { protected static Logger logger = Logger.getLogger("controller"); @Resource(name="personService") private PersonService personService; /** * Handles and retrieves all persons and show it in a JSP page * * @return the name of the JSP page */ @RequestMapping(value = "/persons", method = RequestMethod.GET) public String getPersons(Model model) { logger.debug("Received request to show all persons"); // Retrieve all persons by delegating the call to PersonService ListThis controller declares the following mappings:persons = personService.getAll(); // Attach persons to the Model model.addAttribute("persons", persons); // This will resolve to /WEB-INF/jsp/personspage.jsp return "personspage"; } /** * Retrieves the add page * * @return the name of the JSP page */ @RequestMapping(value = "/persons/add", method = RequestMethod.GET) public String getAdd(Model model) { logger.debug("Received request to show add page"); // Create new Person and add to model // This is the formBackingOBject model.addAttribute("personAttribute", new Person()); // This will resolve to /WEB-INF/jsp/addpage.jsp return "addpage"; } /** * Adds a new person by delegating the processing to PersonService. * Displays a confirmation JSP page * * @return the name of the JSP page */ @RequestMapping(value = "/persons/add", method = RequestMethod.POST) public String add(@ModelAttribute("personAttribute") Person person) { logger.debug("Received request to add new person"); // The "personAttribute" model has been passed to the controller from the JSP // We use the name "personAttribute" because the JSP uses that name // Call PersonService to do the actual adding personService.add(person); // This will resolve to /WEB-INF/jsp/addedpage.jsp return "addedpage"; } /** * Deletes an existing person by delegating the processing to PersonService. * Displays a confirmation JSP page * * @return the name of the JSP page */ @RequestMapping(value = "/persons/delete", method = RequestMethod.GET) public String delete(@RequestParam(value="id", required=true) Integer id, Model model) { logger.debug("Received request to delete existing person"); // Call PersonService to do the actual deleting personService.delete(id); // Add id reference to Model model.addAttribute("id", id); // This will resolve to /WEB-INF/jsp/deletedpage.jsp return "deletedpage"; } /** * Retrieves the edit page * * @return the name of the JSP page */ @RequestMapping(value = "/persons/edit", method = RequestMethod.GET) public String getEdit(@RequestParam(value="id", required=true) Integer id, Model model) { logger.debug("Received request to show edit page"); // Retrieve existing Person and add to model // This is the formBackingOBject model.addAttribute("personAttribute", personService.get(id)); // This will resolve to /WEB-INF/jsp/editpage.jsp return "editpage"; } /** * Edits an existing person by delegating the processing to PersonService. * Displays a confirmation JSP page * * @return the name of the JSP page */ @RequestMapping(value = "/persons/edit", method = RequestMethod.POST) public String saveEdit(@ModelAttribute("personAttribute") Person person, @RequestParam(value="id", required=true) Integer id, Model model) { logger.debug("Received request to update person"); // The "personAttribute" model has been passed to the controller from the JSP // We use the name "personAttribute" because the JSP uses that name // We manually assign the id because we disabled it in the JSP page // When a field is disabled it will not be included in the ModelAttribute person.setId(id); // Delegate to PersonService for editing personService.edit(person); // Add id reference to Model model.addAttribute("id", id); // This will resolve to /WEB-INF/jsp/editedpage.jsp return "editedpage"; } }
/persons - for retrieving all persons /persons/add (GET) - displays the Add New form /persons/add (POST) - saves the new person /persons/delete - deletes an existing person /persons/edit (GET) - displays the Edit form /persons/edit (POST) - saves the edited person
Each mapping delegates the call to the PersonService. When the PersonService is done processing, the controller then forwards the request to a JSP page that displays a confirmation message. Here are the JSP pages.
personspage.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <h1>Persons</h1> <c:url var="addUrl" value="/krams/main/persons/add" /> <table style="border: 1px solid; width: 500px; text-align:center"> <thead style="background:#fcf"> <tr> <th>First Name</th> <th>Last Name</th> <th>Money</th> <th colspan="3"></th> </tr> </thead> <tbody> <c:forEach items="${persons}" var="person"> <c:url var="editUrl" value="/krams/main/persons/edit?id=${person.id}" /> <c:url var="deleteUrl" value="/krams/main/persons/delete?id=${person.id}" /> <tr> <td><c:out value="${person.firstName}" /></td> <td><c:out value="${person.lastName}" /></td> <td><c:out value="${person.money}" /></td> <td><a href="${editUrl}">Edit</a></td> <td><a href="${deleteUrl}">Delete</a></td> <td><a href="${addUrl}">Add</a></td> </tr> </c:forEach> </tbody> </table> <c:if test="${empty persons}"> There are currently no persons in the list. <a href="${addUrl}">Add</a> a person. </c:if> </body> </html>editpage.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <h1>Edit Person</h1> <c:url var="saveUrl" value="/krams/main/persons/edit?id=${personAttribute.id}" /> <form:form modelAttribute="personAttribute" method="POST" action="${saveUrl}"> <table> <tr> <td><form:label path="id">Id:</form:label></td> <td><form:input path="id" disabled="true"/></td> </tr> <tr> <td><form:label path="firstName">First Name:</form:label></td> <td><form:input path="firstName"/></td> </tr> <tr> <td><form:label path="lastName">Last Name</form:label></td> <td><form:input path="lastName"/></td> </tr> <tr> <td><form:label path="money">Money</form:label></td> <td><form:input path="money"/></td> </tr> </table> <input type="submit" value="Save" /> </form:form> </body> </html>addpage.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <h1>Create New Person</h1> <c:url var="saveUrl" value="/krams/main/persons/add" /> <form:form modelAttribute="personAttribute" method="POST" action="${saveUrl}"> <table> <tr> <td><form:label path="firstName">First Name:</form:label></td> <td><form:input path="firstName"/></td> </tr> <tr> <td><form:label path="lastName">Last Name</form:label></td> <td><form:input path="lastName"/></td> </tr> <tr> <td><form:label path="money">Money</form:label></td> <td><form:input path="money"/></td> </tr> </table> <input type="submit" value="Save" /> </form:form> </body> </html>editedpage.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ page import="java.util.Date" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <h1>Persons</h1> <p>You have edited a person with id ${id} at <%= new java.util.Date() %></p> <c:url var="mainUrl" value="/krams/main/persons" /> <p>Return to <a href="${mainUrl}">Main List</a></p> </body> </html>addedpage.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ page import="java.util.Date" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <h1>Persons</h1> <p>You have added a new person at <%= new java.util.Date() %></p> <c:url var="mainUrl" value="/krams/main/persons" /> <p>Return to <a href="${mainUrl}">Main List</a></p> </body> </html>deletedpage.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ page import="java.util.Date" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <h1>Persons</h1> <p>You have deleted a person with id ${id} at <%= new java.util.Date() %></p> <c:url var="mainUrl" value="/krams/main/persons" /> <p>Return to <a href="${mainUrl}">Main List</a></p> </body> </html>
Let's complete our Spring MVC application by declaring the required configurations.
To enable Spring MVC we need to add in the web.xml
web.xml
<servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>/krams/*</url-pattern> </servlet-mapping> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>Take note of the URL pattern. When accessing any pages in our MVC application, the host name must be appended with
/kramsIn the web.xml we declared a servlet-name spring. By convention, we must declare a spring-servlet.xml as well.
spring-servlet.xml
<!-- Declare a view resolver --> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" />By convention, we must declare an applicationContext.xml as well.
applicationContext.xml
<!-- Activates various annotations to be detected in bean classes --> <context:annotation-config /> <!-- Scans the classpath for annotated components that will be auto-registered as Spring beans. For example @Controller and @Service. Make sure to set the correct base-package--> <context:component-scan base-package="org.krams.tutorial" /> <!-- Configures the annotation-driven Spring MVC Controller programming model. Note that, with Spring 3.0, this tag works in Servlet MVC only! --> <mvc:annotation-driven /> <!-- Load Hibernate related configuration --> <import resource="hibernate-context.xml" />Notice in the applicationContext.xml, we declared the following import:
<import resource="hibernate-context.xml" />This contains the Hibernate configuration files
<?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:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd "> <context:property-placeholder location="/WEB-INF/spring.properties" /> <!-- Enable annotation style of managing transactions --> <tx:annotation-driven transaction-manager="transactionManager" /> <!-- Declare the Hibernate SessionFactory for retrieving Hibernate sessions --> <!-- See http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/orm/hibernate3/annotation/AnnotationSessionFactoryBean.html --> <!-- See http://docs.jboss.org/hibernate/stable/core/api/index.html?org/hibernate/SessionFactory.html --> <!-- See http://docs.jboss.org/hibernate/stable/core/api/index.html?org/hibernate/Session.html --> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" p:dataSource-ref="dataSource" p:configLocation="${hibernate.config}" p:packagesToScan="org.krams.tutorial"/> <!-- Declare a datasource that has pooling capabilities--> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close" p:driverClass="${app.jdbc.driverClassName}" p:jdbcUrl="${app.jdbc.url}" p:user="${app.jdbc.username}" p:password="${app.jdbc.password}" p:acquireIncrement="5" p:idleConnectionTestPeriod="60" p:maxPoolSize="100" p:maxStatements="50" p:minPoolSize="10" /> <!-- Declare a transaction manager--> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager" p:sessionFactory-ref="sessionFactory" /> </beans>We basically encapsulated all Hibernate and Spring related configurations in this one XML file. Here's what happening within the config:
1. Enable transaction support through Spring annotations:
<tx:annotation-driven transaction-manager="transactionManager" />2. Declare the Hibernate SessionFactory:
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" p:dataSource-ref="dataSource" p:configLocation="${hibernate.config}" p:packagesToScan="org.krams.tutorial"/>A SessionFactory is a factory that produces Session objects. It's analogous to a real Car Factory or Car Assemblies where its job is to make cars for humans.
What is a Session?
The main function of the Session is to offer create, read and delete operations for instances of mapped entity classes.
Source: http://docs.jboss.org/hibernate/stable/core/api/index.html?org/hibernate/Session.html
A SessionFactory requires a datasource. The datasource in this tutorial is the database.
A SessionFactory requires a configLocation. The configLocation contains Hibernate-specific configurations. Here's our Hibernate-specific config file:
hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- We're using MySQL database so the dialect needs to MySQL as well--> <property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property> <!-- Enable this to see the SQL statements in the logs--> <property name="show_sql">false</property> <!-- This will drop our existing database and re-create a new one. Existing data will be deleted! --> <property name="hbm2ddl.auto">create</property> </session-factory> </hibernate-configuration>Here we declared the type of database to be used. We are using MySQL so we use a MySQL dialect. We're using a specialized MySQL5InnoDBDialect because we decided to use the InnoDB storage engine for MySQL.
What's MySQL InnoDB?
InnoDB is a transaction-safe (ACID compliant) storage engine for MySQL that has commit, rollback, and crash-recovery capabilities to protect user data.Returning back to the SessionFactory bean declaration, it also requires the property packagesToScan. This is to indicate where our annotated entities are located. In this tutorial, it's under the org.krams.tutorial
Source: http://dev.mysql.com/doc/refman/5.0/en/innodb-storage-engine.html
3. Declare a datasource:
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close" p:driverClass="${app.jdbc.driverClassName}" p:jdbcUrl="${app.jdbc.url}" p:user="${app.jdbc.username}" p:password="${app.jdbc.password}" p:acquireIncrement="5" p:idleConnectionTestPeriod="60" p:maxPoolSize="100" p:maxStatements="50" p:minPoolSize="10" />Our datasource uses C3P0 for pooling to allow efficient access to our database. Why do we need to wrap our datasource with a connection pool?
JDBC connections are often managed via a connection pool rather than obtained directly from the driver. Examples of connection pools include BoneCP, C3P0 and DBCP.
Source: http://en.wikipedia.org/wiki/Java_Database_Connectivity
What is Pooling?
In software engineering, a connection pool is a cache of database connections maintained so that the connections can be reused when future requests to the database are required. Connection pools are used to enhance the performance of executing commands on a database. Opening and maintaining a database connection for each user, especially requests made to a dynamic database-driven website application, is costly and wastes resources. In connection pooling, after a connection is created, it is placed in the pool and it is used over again so that a new connection does not have to be established.
Source: http://en.wikipedia.org/wiki/Connection_pool
For more info on configuring C3P0, you can check this reference from JBoss: HowTo configure the C3P0 connection pool. For a list of other pooling providers, see Open Source Database Connection Pools
The database-specific configuration are contained within a properties file.
spring.properties
mydatabase is the name of our MySQL dabatase.
As an alternative, we can enter these properties directly within the hibernate-context.xml
This is exactly similar to the following:
The benefit of using a separate properties file is we encapsulate all database-specific configs within a separate file. The hibernate-context.xml purpose is to encapsulate Hibernate-related config not database properties.
That's it. We've completed our application. We've managed to setup a simple Spring MVC 3 application that uses Hibernate Annotations to encapsulate persistence access to a MySQL database. We've also leveraged Spring's simple MVC programming model.
To access the main page, enter the following URL:
http://localhost:8080/spring-hibernate-mysql/krams/main/personsThe best way to learn further is to try the actual application.
Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring-mvc-hibernate-annotations-integration-tutorial/
You can download the project as a Maven build. Look for the spring-hibernate-mysql.zip in the Download sections.
You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run
If you want to learn more about Spring MVC and integration with other technologies, feel free to read my other tutorials in the Tutorials section.
Share the joy:
|
Subscribe by reader Subscribe by email Share