Hibernate – JPA

pom.xml

				
					<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.0.2</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.brains.jpa.hibernate</groupId>
	<artifactId>jpa-hibernate-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>jpa-hibernate-demo</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

				
			

These annotions are provided by Hibernate which can track the timestamp of created and updated entries. JPA does not provide these annotations.

@UpdateTimestamp

@CreationTimestamp

				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

import java.time.LocalDateTime;

import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table
public class Course {
	
	@Id
	@GeneratedValue
	private Long id;
	
	private String name;
	
	@UpdateTimestamp
	private LocalDateTime lastUpdatedDate;
	
	@CreationTimestamp
	private LocalDateTime createdDate;
	
	public Course(String name) {
		this.name = name;
	}
}

				
			

application.properties

				
					
spring.datasource.url=jdbc:h2:mem:testdb;NON_KEYWORDS=USER;DB_CLOSE_ON_EXIT=FALSE
spring.h2.console.enabled=true
#spring.data.jpa.repositories.bootstrap-mode=default
spring.jpa.defer-datasource-initialization=true

# Turn Statistics ON
spring.jpa.properties.hibernate.generate_statistics=true
logging.level.org.hibernate.stat=debug
logging.level.org.hibernate=debug
# Show all queries
spring.jpa.show-sql=true

# Format the queries
spring.jpa.properties.hibernate.format_sql=true

# Enable seeing the parameters with query
logging.level.org.hibernate.type=trace
				
			

Normally you should use CURRENT_DATECURRENT_TIMESTAMP, or LOCALTIMESTAMP in H2. They all have standard-compliant implementations and work as expected.

H2 should probably reject SYSDATE and all similar functions in all compatibility modes, with exception for Oracle compatibility mode in which it should be a keyword.

 

data.sql

				
					insert into course(id, name, created_date, last_updated_date) 
values(10001, 'JPA in 5 steps', LOCALTIMESTAMP, LOCALTIMESTAMP);

insert into course(id, name, created_date, last_updated_date) 
values(10002, 'JDBC in 10 steps', LOCALTIMESTAMP, LOCALTIMESTAMP);

insert into course(id, name, created_date, last_updated_date) 
values(10003, 'JPQL in 50 steps', LOCALTIMESTAMP, LOCALTIMESTAMP);
				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.repository;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.brains.jpa.hibernate.jpahibernatedemo.entity.Course;

import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;

@Repository
@Transactional
public class CourseRepository {

	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@Autowired
	EntityManager em;
	
	public Course findById(Long id) {
		return em.find(Course.class, id);
	}
	
	public void deleteById(Long id) {
		Course course = findById(id);
		em.remove(course);
	}
	
	public Course save(Course course) {
		if(course.getId() == null) {
			//insert
			em.persist(course);
		}else {
			//update
			em.merge(course);
		}
		return course;
	}
	
	public void playWithEntityManager() {
		Course course1 = new Course("AngularJs in 100 Steps");
		em.persist(course1);
		
		Course course2 = findById(10001L);
		course2.setName("JPA in 5 steps -- updated");
	}
}

				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.brains.jpa.hibernate.jpahibernatedemo.repository.CourseRepository;

@SpringBootApplication
public class JpaHibernateDemoApplication implements CommandLineRunner {

	public static void main(String[] args) {
		SpringApplication.run(JpaHibernateDemoApplication.class, args);
	}

	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@Autowired
	private CourseRepository courseRepository;
	
	@Override
	public void run(String... args) throws Exception {
		courseRepository.playWithEntityManager();
	}
}
				
			

Related Tutorials

SpringBoot JPA with H2

JPA is Java Persistence API, is an interface and Hibernate is an implementation of JPA and is an ORM Framework. ORM stands for Object-Relational Mapping

 

 

pom.xml

				
					<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.0.2</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.brains.database</groupId>
	<artifactId>database-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>database-demo</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

				
			

application.properties

				
					#spring.datasource.url=jdbc:h2:mem:testdb;NON_KEYWORDS=USER
spring.datasource.url=jdbc:h2:mem:public;MODE=MYSQL;DATABASE_TO_UPPER=false;INIT=CREATE SCHEMA IF NOT EXISTS public
spring.h2.console.enabled=true
spring.data.jpa.repositories.bootstrap-mode=default
spring.jpa.defer-datasource-initialization=true

spring.jpa.show-sql=true
				
			

See the UI for the H2 database by typing this url

				
					http://localhost:8080/h2-console
				
			

Add data.sql in the resources folder and it would automatically called at startup. Load some data in the  table by adding some records in the same data.sql file.

data.sql

				
					insert into person (id, name, location, birth_date)
values(10001,'Ranga','Hyderabad', now() );
insert into person (id, name, location, birth_date)
values(10002,'John','Guntur', now() );
insert into person (id, name, location, birth_date)
values(10003,'Ravi','Ahmedabad', now() );
				
			

Create PersonJdbcDao which can interact with the database. Here all the magic happens using Autoconfiguration by SpringBoot. @JdbcTemplate is the class responsible such database interactions.

				
					package com.brains.database.databasedemo.bean;


import java.util.Date;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.NamedQuery;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data @AllArgsConstructor @NoArgsConstructor
@NamedQuery(name = "find_all_persons", query = "select p from Person p")
public class Person {

	@Id
	@GeneratedValue
	private int id;
	
	private String name;
	private String location;
	private Date birth_date;
	
	public Person(String name, String location, Date birth_date) {
		super();
		this.name = name;
		this.location = location;
		this.birth_date = birth_date;
	}
}
				
			
				
					package com.brains.database.databasedemo.repository;

import java.util.List;

import org.springframework.stereotype.Repository;

import com.brains.database.databasedemo.bean.Person;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.TypedQuery;
import jakarta.transaction.Transactional;

@Repository
@Transactional
public class PersonJpaRepository {

	@PersistenceContext
	EntityManager entityManager;
	
	public List<Person> findAll(){
		TypedQuery<Person> personNamedQuery = entityManager.createNamedQuery("find_all_persons", Person.class);
		return personNamedQuery.getResultList();
	}
	
	public Person findById(int id) {
		return entityManager.find(Person.class, id);
	}
	
	public Person update(Person person) {
		return entityManager.merge(person);
	}
	
	public Person insert(Person person) {
		return entityManager.merge(person);
	}
	
	public void delete(int id) {
		Person person = findById(id);
		entityManager.remove(person);
	}
}

				
			

Extend the main applicatino with CommandLineRunner so that run() method can be executed while application startup.

				
					package com.brains.database.databasedemo;

import java.util.Date;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.brains.database.databasedemo.bean.Person;
import com.brains.database.databasedemo.repository.PersonJpaRepository;

@SpringBootApplication
public class SpringJpaDemoApplication implements CommandLineRunner {

	public static void main(String[] args) {
		SpringApplication.run(SpringJpaDemoApplication.class, args);
	}

	@Autowired
	PersonJpaRepository jpaRepository;

	private Logger logger = LoggerFactory.getLogger(this.getClass());

	@Override
	public void run(String... args) throws Exception {
		logger.info("Peson found: -> {}", jpaRepository.findById(10002));
		logger.info("Insert new person -> {}", jpaRepository.insert(new Person("Suresh", "Ahmedabad", new Date())));
		jpaRepository.delete(10001);
		
		logger.info("All persons: -> {}", jpaRepository.findAll());
	}
}
				
			

Running the Application

				
					2023-02-21T00:28:37.518+05:30  INFO 26412 --- [           main] SpringJpaDemoApplication$$SpringCGLIB$$0 : Peson found: -> Person(id=10002, name=John, location=Guntur, birth_date=2023-02-21 00:28:37.298462)
Hibernate: select next value for person_seq
Hibernate: insert into person (birth_date, location, name, id) values (?, ?, ?, ?)
2023-02-21T00:28:37.527+05:30  INFO 26412 --- [           main] SpringJpaDemoApplication$$SpringCGLIB$$0 : Insert new person -> Person(id=1, name=Suresh, location=Ahmedabad, birth_date=2023-02-21 00:28:37.518)
Hibernate: select p1_0.id,p1_0.birth_date,p1_0.location,p1_0.name from person p1_0 where p1_0.id=?
Hibernate: delete from person where id=?
Hibernate: select p1_0.id,p1_0.birth_date,p1_0.location,p1_0.name from person p1_0
2023-02-21T00:28:37.551+05:30  INFO 26412 --- [           main] SpringJpaDemoApplication$$SpringCGLIB$$0 : All persons: -> [Person(id=1, name=Suresh, location=Ahmedabad, birth_date=2023-02-21 00:28:37.518), Person(id=10002, name=John, location=Guntur, birth_date=2023-02-21 00:28:37.298462), Person(id=10003, name=Ravi, location=Ahmedabad, birth_date=2023-02-21 00:28:37.298631)]

				
			

Related Tutorials

SpringBoot JDBC with H2

Adding the dependencies of JDBC and H2 in the pom file.

 

 

pom.xml

				
					<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.0.2</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.brains.database</groupId>
	<artifactId>database-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>database-demo</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

				
			

application.properties

				
					spring.datasource.url=jdbc:h2:mem:testdb;NON_KEYWORDS=USER
spring.h2.console.enabled=true
spring.data.jpa.repositories.bootstrap-mode=default
spring.jpa.defer-datasource-initialization=true
				
			

See the UI for the H2 database by typing this url

				
					http://localhost:8080/h2-console
				
			

Add data.sql in the resources folder and it would automatically called at startup. Load some data in the  table by adding some records in the same data.sql file.

data.sql

				
					create table person
(
	id integer not null,
	name varchar(255) not null,
	location varchar(255),
	birth_date timestamp,
	primary key(id)
);

// add few records
insert into person (id, name, location, birth_date)
values(10001,'Ranga','Hyderabad', now() );
insert into person (id, name, location, birth_date)
values(10002,'John','Guntur', now() );
insert into person (id, name, location, birth_date)
values(10003,'Ravi','Ahmedabad', now() );
				
			

Create PersonJdbcDao which can interact with the database. Here all the magic happens using Autoconfiguration by SpringBoot. @JdbcTemplate is the class responsible such database interactions.

				
					package com.brains.database.databasedemo.bean;


import java.util.Date;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data @AllArgsConstructor @NoArgsConstructor
public class Person {

	private int id;
	private String name;
	private String location;
	private Date birth_date;
}

				
			
				
					package com.brains.database.databasedemo.bean;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data @AllArgsConstructor @NoArgsConstructor
public class CustomPerson {

	private int id;
	private String name;
}

				
			
				
					package com.brains.database.databasedemo.repository;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

import com.brains.database.databasedemo.bean.Person;
import com.brains.database.databasedemo.bean.CustomPerson;

@Repository
public class PersonJdbcDao {

	@Autowired
	JdbcTemplate jdbcTemplate;
	
	class PersonRowMapper implements RowMapper<CustomPerson>{

		@Override
		public CustomPerson mapRow(ResultSet rs, int rowNum) throws SQLException {
			CustomPerson person = new CustomPerson();
			person.setId(rs.getInt("id"));
			person.setName(rs.getString("name"));
			return person;
		}
	}
	
	public List<CustomPerson> findAllUsingRowMapper(){
		return jdbcTemplate.query("select * from person", 
				new PersonRowMapper()); 
	}
	
	public List<Person> findAll(){
		return jdbcTemplate.query("select * from person", 
				new BeanPropertyRowMapper<Person>(Person.class)); 
	}

	
	public Person getPersonById(int id) {
		return jdbcTemplate.queryForObject("select * from person where id = ?", 
				new Object[] {id},
				new BeanPropertyRowMapper<Person>(Person.class));
	}
	
	public int deleteById(int id) {
		return jdbcTemplate.update("delete from person where id =?", 
				new Object[] {id}
				);
	}
	
	public int insertPerson(Person person) {
		return jdbcTemplate.update("insert into person (id, name, location, birth_date)\n"
				+ "values(?,?,?,?)",
				new Object[] {
						person.getId(), person.getName(), 
						person.getLocation(), 
						new Timestamp(person.getBirth_date().getTime())});
	}
	
	public int updatePerson(Person person) {
		return jdbcTemplate.update(
				"update person \n"
				+ " set name = ?, location = ?, birth_date = ? \n"
				+ " where id = ? ",
				new Object[] {
						person.getName(), 
						person.getLocation(), 
						new Timestamp(person.getBirth_date().getTime()),
						person.getId()}
				);
	}
}

				
			

Extend the main applicatino with CommandLineRunner so that run() method can be executed while application startup.

				
					package com.brains.database.databasedemo;

import java.util.Date;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.brains.database.databasedemo.bean.Person;
import com.brains.database.databasedemo.repository.PersonJdbcDao;

@SpringBootApplication
public class DatabaseDemoApplication implements CommandLineRunner {

	public static void main(String[] args) {
		SpringApplication.run(DatabaseDemoApplication.class, args);
	}

	@Autowired
	PersonJdbcDao jdbcDao;
	
	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@Override
	public void run(String... args) throws Exception {
		logger.info("All users using RowMapper: -> {}", jdbcDao.findAllUsingRowMapper());
		logger.info("All users using BeanPropertyRowMapper: -> {}", jdbcDao.findAll());
		
		logger.info("Peson found: -> {}", jdbcDao.getPersonById(10002));
		logger.info("Delete Peson: -> {}", jdbcDao.deleteById(10002));
		
		logger.info("Insert new person -> {}", jdbcDao.insertPerson(new Person(10004, "Suresh", "Ahmedabad", new Date())));
		logger.info("Update Person id 10003 -> {}", jdbcDao.updatePerson(new Person(10003, "Aman", "Ahmedabad", new Date())));
		logger.info("All users: -> {}", jdbcDao.findAll());
	}
}

				
			

Running the Application

				
					2023-02-20T20:16:23.124+05:30  INFO 120598 --- [           main] .DatabaseDemoApplication$$SpringCGLIB$$0 : All users using RowMapper: -> [CustomPerson(id=10001, name=Ranga), CustomPerson(id=10002, name=John), CustomPerson(id=10003, name=Ravi)]
2023-02-20T20:16:23.129+05:30  INFO 120598 --- [           main] .DatabaseDemoApplication$$SpringCGLIB$$0 : All users using BeanPropertyRowMapper: -> [Person(id=10001, name=Ranga, location=Hyderabad, birth_date=2023-02-20 20:16:22.657722), Person(id=10002, name=John, location=Guntur, birth_date=2023-02-20 20:16:22.659073), Person(id=10003, name=Ravi, location=Ahmedabad, birth_date=2023-02-20 20:16:22.659391)]
2023-02-20T20:16:23.142+05:30  INFO 120598 --- [           main] .DatabaseDemoApplication$$SpringCGLIB$$0 : Peson found: -> Person(id=10002, name=John, location=Guntur, birth_date=2023-02-20 20:16:22.659073)
2023-02-20T20:16:23.144+05:30  INFO 120598 --- [           main] .DatabaseDemoApplication$$SpringCGLIB$$0 : Delete Peson: -> 1
2023-02-20T20:16:23.145+05:30  INFO 120598 --- [           main] .DatabaseDemoApplication$$SpringCGLIB$$0 : Insert new person -> 1
2023-02-20T20:16:23.148+05:30  INFO 120598 --- [           main] .DatabaseDemoApplication$$SpringCGLIB$$0 : Update Person id 10003 -> 1
2023-02-20T20:16:23.149+05:30  INFO 120598 --- [           main] .DatabaseDemoApplication$$SpringCGLIB$$0 : All users: -> [Person(id=10001, name=Ranga, location=Hyderabad, birth_date=2023-02-20 20:16:22.657722), Person(id=10003, name=Aman, location=Ahmedabad, birth_date=2023-02-20 20:16:23.145), Person(id=10004, name=Suresh, location=Ahmedabad, birth_date=2023-02-20 20:16:23.144)]

				
			

Related Tutorials

UserDetailsService : Loading UserDetails from database

Resources

Pom.xml

				
					<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.8</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>userdetailService-db</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>userdetailService-db</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

				
			

application.properties

				
					spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
 
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto= update

spring.h2.console.enabled=true
# default path: h2-console
spring.h2.console.path=/h2-ui
				
			

Database entities

In order to load user information from the database, we need to use spring JDBC or spring JPA. For the sake of completeness, I’m using spring JPA and here is a simple UserAccount and UserRole entities.

				
					package com.example.demo.model;

import java.util.List;

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 lombok.Data;

@Entity
@Data
public class UserAccount {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer id;
	
	@Column(unique = true)
	private String username;
	
	private String password;
	
	private boolean active;
	
	@OneToMany(cascade = CascadeType.ALL)
	private List<UserRole> userRoles;
}

				
			
				
					package com.example.demo.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;

import lombok.Data;

@Entity
@Data
public class UserRole {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer id;
	
	private String role;
	
	@ManyToOne
	private UserAccount userAccount;
}

				
			
  1. Here the UserRole is to show how a single user can have multiple roles (@OneToMany).
  2. The username column is marked as unique due to the nature of how usernames should be. However, it’s up to you how you want to design the database entries.
  3. I’m using Lombok hence there are no getters and setters.

With the entities ready, let’s write the necessary repository methods for our CustomUserDetailService. The following definition would return an UserAccount entity based on the username.

				
					package com.example.demo.repository;

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

import com.example.demo.model.UserAccount;

@Repository
public interface UserAccountRepository extends JpaRepository<UserAccount, Integer> {
	UserAccount findByUsername(String username);
}
				
			

UserDetails

The UserDetailsService service interface is supposed to return an implementation of org.springframework.security.core.userdetails.UserDetails. So first we need to define a CustomUserDetails class backed by an UserAccount. Here is how I implemented them. However, it is up to you to implement this class differently if you have to.

				
					package com.example.demo.service;

import java.util.Collection;
import java.util.Collections;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import com.example.demo.model.UserAccount;

public class CustomUserDetails implements UserDetails {

	private final UserAccount userAccount;
	
	public CustomUserDetails(UserAccount userAccount) {
		this.userAccount = userAccount;
	}
	
	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		return Collections.singletonList(new GrantedAuthority() {
			
			@Override
			public String getAuthority() {
				return "USER";
			}
		});
	}

	@Override
	public String getPassword() {
		return userAccount.getPassword();
	}

	@Override
	public String getUsername() {
		return userAccount.getUsername();
	}

	@Override
	public boolean isAccountNonExpired() {
		return userAccount.isActive();
	}

	@Override
	public boolean isAccountNonLocked() {
		return userAccount.isActive();
	}

	@Override
	public boolean isCredentialsNonExpired() {
		return userAccount.isActive();
	}

	@Override
	public boolean isEnabled() {
		return userAccount.isActive();
	}

}

				
			

The getAuthorities() method of UserDetails needs a list of GrantedAuthority. For now, I have hardcoded it to return only USER the role. Also, I have written a getUserAccount() so that I can use this to get hold of the current user entity.

Loading user details from the database

With all the above set, All we need is UserDetailService implementation. As we have already established our database entities and repositories, let’s write our performance and mark it a bean with the help of @Component annotation.

				
					package com.example.demo.service;

import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

import com.example.demo.model.UserAccount;
import com.example.demo.repository.UserAccountRepository;

@Component
public class DatabaseUserDetailsService implements UserDetailsService {

	private final UserAccountRepository accountRepository;
	
	public DatabaseUserDetailsService(UserAccountRepository accountRepository) {
		this.accountRepository = accountRepository;
	}
	
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		UserAccount userAccount = accountRepository.findByUsername(username);
		if(userAccount == null) {
			throw new UsernameNotFoundException("User with username ["+username+"] not found");
		}
		return new CustomUserDetails(userAccount);
	}
}
				
			

This is as simple as it can get. The contract for this method is that if the system is not able to find a user for a given username, the method should throw a UsernameNotFoundException message. Once the method gets a UserAccount record, It is converted into CustomUserDetails and presented in a security context.

				
					package com.example.demo.controller;

import java.util.Arrays;

import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo.model.UserAccount;
import com.example.demo.model.UserRole;
import com.example.demo.repository.UserAccountRepository;

@RestController
public class UserController {

	private UserAccountRepository userAccountRepository;
	private PasswordEncoder passwordEncoder;
	
	public UserController(UserAccountRepository userAccountRepository, PasswordEncoder passwordEncoder) {
        this.userAccountRepository = userAccountRepository;
        this.passwordEncoder = passwordEncoder;
    }
	
	@GetMapping("/")
	public String greeting() {
		return "Hey there ";
	}
	
	@GetMapping("/login")
	public String login(Authentication auth) {
		return "Welcome there "+ auth.getName();
	}
	
	@PostMapping("/register")
	public UserAccount register(@RequestParam("username") String username, @RequestParam("password") String password) {
		UserAccount userAccount = new UserAccount();
		userAccount.setUsername(username);
		userAccount.setPassword(passwordEncoder.encode(password));
		UserRole role = new UserRole();
		role.setRole("USER");
		userAccount.setUserRoles(Arrays.asList(role));
		userAccount.setActive(true);
		return userAccountRepository.save(userAccount);
	}
}

				
			

To make sure register.html and /register accessible without requiring login, exclude them from web security.

				
					package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.authorizeRequests()
				.antMatchers("/").permitAll()
				.anyRequest().authenticated()
				.and()
			.httpBasic()
				.and()
			.logout()
				.permitAll();
	}
	
	@Override
	public void configure(WebSecurity web) throws Exception {
		web.ignoring()
			.antMatchers(HttpMethod.POST, "/register")
			.antMatchers("/h2-ui/**");
	}
	
	@Bean
	PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}
}

				
			

The Result

With all the above in place, let’s test by registering a user and logging in using the same application.

 

Register new user

 

 

 

Related Tutorials