Create the following folder structure:

pom.xml: (if using Gradle, replace pom.xml with build.gradle)
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>net.maxjava</groupId> <artifactId>SpringRESTAPIHibernateOnly</artifactId> <version>1.0</version> <packaging>war</packaging> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>6.1.6</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>6.1.6</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.4</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>5.3.10.Final</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-c3p0</artifactId> <version>6.5.2.Final</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.30</version> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.13.0</version> <configuration> <source>17</source> <target>17</target> </configuration> </plugin> </plugins> </build> </project>
plugins { id 'java' id 'war' } java { sourceCompatibility = 17 targetCompatibility = 17 } group 'net.maxjava' version '1.0' repositories { mavenCentral() } dependencies { implementation group: 'org.springframework', name: 'spring-webmvc', version: '6.1.6' implementation group: 'org.springframework', name: 'spring-orm', version: '6.1.6' implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.15.4' implementation group: 'mysql', name: 'mysql-connector-java', version: '8.0.33' implementation group: 'org.hibernate', name: 'hibernate-core', version: '6.5.2.Final' implementation group: 'org.hibernate', name: 'hibernate-c3p0', version: '6.5.2.Final' compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.30' annotationProcessor group: 'org.projectlombok', name: 'lombok', version: '1.18.30' }
Some notes about pom.xml:
- spring-webmvc for necessary Spring REST controllers and its dependencies
- spring-orm for Hibernate session related classes like session factories
- jackson-databind is for the java object serialization and deserialization
- hibernate-core for Hibernate classes
- hibernate-c3p0 is for datasource
- lombok to remove boilerplate code like get and set
- maven-compiler-plugin is for compiling the code using source version 17 and target version 17
- A war artifact will be created for deployment to servlet containers
Some notes about build.gradle:
- spring-webmvc for necessary Spring REST controllers and its dependencies
- spring-orm for Hibernate session related classes like session factories
- jackson-databind is for the java object serialization and deserialization
- hibernate-core for Hibernate classes
- hibernate-c3p0 is for datasource
- lombok to remove boilerplate code like get and set
- sourceCompatibility and targetCompatibility is for compiling the code using source version 17 and target version 17
- A war artifact will be created for deployment to servlet containers
web.xml:
<web-app> <display-name>Spring REST API with Hibernate</display-name> <servlet> <servlet-name>SpringRESTAPIHibernate</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/myApplicationContext.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>SpringRESTAPIHibernate</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
Some notes about web.xml:
- org.springframework.web.servlet.DispatcherServlet is the servlet class for Spring
- /WEB-INF/myApplicationContext.xml is the location of the Spring context configuration file
myApplicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <context:component-scan base-package="controller,repository" /> <tx:annotation-driven transaction-manager="transactionManager"/> <mvc:annotation-driven> <mvc:message-converters> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/> </mvc:message-converters> </mvc:annotation-driven> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mydb"/> <property name="user" value="myuser"/> <property name="password" value="mypassword"/> <property name="initialPoolSize" value="10"/> </bean> <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mappingResources"> <list> <value>customer.hbm.xml</value> </list> </property> </bean> <bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory"/> </bean> </beans>
Some notes about myApplicationContext.xml:
- <context:component-scan base-package="controller,repository"/> is for Spring to look for classes in controller,repository packages to create beans
- <tx:annotation-driven transaction-manager="transactionManager"/> is to allow Spring to use annotation for transaction management, and the transaction-manager is the bean org.springframework.orm.hibernate5.HibernateTransactionManager defined below
- <mvc:message-converters> is to specify library for json serialization and deserialization
- <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> is to specify the datasource for use in LocalSessionFactoryBean
- <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean"> is to create LocalSessionFactoryBean for autowiring in the code to create Hibernate Session
customer.hbm.xml:
<?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="repository.CustomerEntity" table="customer"> <id name="name" type="string"> <column name="Name"/> </id> <property name="email" type="string"> <column name="Email"/> </property> <property name="createdDate" type="timestamp"> <column name="CreatedDate"/> </property> </class> </hibernate-mapping>
Some notes about customer.hbm.xml:
- The mapping is based on the following table schema:
CREATE TABLE customer ( Name VARCHAR(50) NOT NULL, Email VARCHAR(50) NULL DEFAULT NULL, CreatedDate TIMESTAMP NULL DEFAULT NULL, PRIMARY KEY (Name) )
MyController.java:
package controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import model.Customer; import repository.CustomerRepository; @RestController @RequestMapping("/myapp") public class MyController { @Autowired private CustomerRepository customerRepository; @GetMapping("/customer") public Customer getCustomer(@RequestParam("name") String name) { Customer customer = customerRepository.getCustomerByName(name); return customer; } @PostMapping("/customer") public void postCustomer(@RequestBody Customer customer) { customerRepository.saveCustomer(customer); } @PutMapping("/customer") public void putCustomer(@RequestBody Customer customer) { customerRepository.updateCustomer(customer); } @DeleteMapping("/customer") public void deleteCustomer(@RequestParam("name") String name) { customerRepository.deleteCustomerByName(name); } }
Some notes about MyController.java:
- This is the Spring REST web service which putting @RestController will do the trick (and make sure the package of this class got scanned in bean creation)
- Autowires CustomerRepository which is the bean to interact with the database
- Provides basic CRUD REST APIs and autowires CustomerRepository to interact with the database
Customer.java:
package model; import lombok.Data; @Data public class Customer { private String name; private String email; }
Some notes about Customer.java:
- This is the object used in the request body and response body of the REST APIs
CustomerEntity.java:
package repository; import lombok.Data; import java.util.Date; @Data public class CustomerEntity { private String name; private String email; private Date createdDate; }
Some notes about CustomerEntity.java:
- This is the mapped class for table "Customer"
CustomerRepository.java:
package repository; import model.Customer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.*; import org.hibernate.SessionFactory; import org.springframework.transaction.annotation.Transactional; import java.util.Date; @Repository public class CustomerRepository { @Autowired private SessionFactory sessionFactory; @Transactional public Customer getCustomerByName(String name) { CustomerEntity customerEntity = sessionFactory.getCurrentSession().get(CustomerEntity.class, name); Customer customer = new Customer(); customer.setEmail(customerEntity.getEmail()); customer.setName(customerEntity.getName()); return customer; } @Transactional public void saveCustomer(Customer customer) { CustomerEntity customerEntity = new CustomerEntity(); customerEntity.setName(customer.getName()); customerEntity.setEmail(customer.getEmail()); customerEntity.setCreatedDate(new Date()); sessionFactory.getCurrentSession().save(customerEntity); } @Transactional public void updateCustomer(Customer customer) { CustomerEntity customerEntity = new CustomerEntity(); customerEntity.setName(customer.getName()); customerEntity.setEmail(customer.getEmail()); customerEntity.setCreatedDate(new Date()); sessionFactory.getCurrentSession().update(customerEntity); } @Transactional public void deleteCustomerByName(String name) { CustomerEntity customerEntity = new CustomerEntity(); customerEntity.setName(name); sessionFactory.getCurrentSession().delete(customerEntity); } }
Some notes about CustomerDao.java:
- Autowires the Hibernate SessionFactory bean
- Put @Transactional around each Hibernate function, even for get, as transaction is needed for get for Hibernate
- Mark this with @Repository as a bean
Sample requests and responses(assume the war file is deployed to <CONTEXT_PATH>):
- Add a new record:
curl --request POST '<CONTEXT_PATH>/myapp/customer' --header 'Content-Type: application/json' --data-raw ' { "name": "Ethan", "email": "ethan@maxjava.net" }'
- Retrieve a record by name
Response:curl --request GET '<CONTEXT_PATH>/myapp/customer?name=Ethan'{ "name": "Ethan", "email": "ethan@maxjava.net" } - Updating a record by name
curl --request PUT '<CONTEXT_PATH>/myapp/customer' --header 'Content-Type: application/json' --data-raw ' { "name": "Ethan", "email": "ethan.new@maxjava.net" }'
- Retrieve the record again by same name, the data is changed
Response:curl --request GET '<CONTEXT_PATH>myapp/customer?name=Ethan'{ "name": "Ethan", "email": "ethan.new@maxjava.net" } - deleting a record by name
curl --request DELETE '<CONTEXT_PATH>/myapp/customer?name=Ethan'