NamedQuery Annotations

With NamedQuery, we can reuse the query in multiple locations.

				
					spring.datasource.url=jdbc:h2:mem:testdb;NON_KEYWORDS=USER;DB_CLOSE_ON_EXIT=FALSE
spring.h2.console.enabled=true
spring.jpa.defer-datasource-initialization=true

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

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

For writing multiple NamedQuery, we need to use the @NamedQueries

				
					@NamedQueries(
	value = {
		@NamedQuery(name = "query_get_all_courses", query = "Select c from Course c"),
		@NamedQuery(name = "query_get_10_courses", query = "select c from Course c where name like '%10%'")
	}
)
public class Course {
				
			
				
					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.NamedQuery;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table
@NamedQuery(name = "query_get_all_courses", query = "Select c from Course c")
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;
	}
}

				
			
				
					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);
				
			

Use NativeQueries when multiple rows are required to be updated/inserted. Or in case there the JPA is not supported and need to use the database functionality directly.

				
					package com.brains.jpa.hibernate.jpahibernatedemo;

import java.util.List;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

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

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

@SpringBootTest(classes = JpaHibernateDemoApplication.class)
class JPQLTest {

	@Autowired
	EntityManager em;
	
	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
//	@Test
	void jpql_basic() {
		Query query = em.createNamedQuery("query_get_all_courses");
		List resultList = query.getResultList();
		logger.info("list - jpql_basic: -> {}", resultList);
	}
	
//	@Test
	void jpql_typed() {
		TypedQuery<Course> query = em.createNamedQuery("query_get_all_courses", Course.class);
		List resultList = query.getResultList();
		logger.info("Course - jpql_typed: -> {}", resultList);
	}

//	@Test
	void jpql_where() {
		TypedQuery<Course> query = em.createNamedQuery("query_get_10_courses", Course.class);
		List resultList = query.getResultList();
		logger.info("Named Course: -> {}", resultList);
	}
	
	//@Test
	void native_queries_basic() {
		Query query = em.createNativeQuery("select * from course", Course.class);
		List resultList = query.getResultList();
		logger.info("list - native_basic: -> {}", resultList);
	}
	
	//@Test
	void native_queries_with_param() {
		Query query = em.createNativeQuery("select * from course where id = ?", Course.class);
		query.setParameter(1, 10001L);
		List resultList = query.getResultList();
		logger.info("list - native_basic: -> {}", resultList);
	}
	
//	@Test
	void native_queries_with_named_param() {
		Query query = em.createNativeQuery("select * from course where id = :id", Course.class);
		query.setParameter("id", 10002L);
		List resultList = query.getResultList();
		logger.info("list - native_basic: -> {}", resultList);
	}
	
	@Test
	@Transactional
	void native_queries_to_update() {
		Query query = em.createNativeQuery("update course set last_updated_date = LOCALTIMESTAMP", Course.class);
		int numOfRowsUpdated = query.executeUpdate();
		logger.info("numOfRowsUpdated: -> {}", numOfRowsUpdated);
	}
}

				
			

Related Tutorials

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

JUnit with JPA

JUnit test cases for the Repositories

Project Structure for adding Test files

				
					<?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>

				
			

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
				
			

data.sql

				
					insert into course(id, name) values(10001, 'JPA in 5 steps');
insert into course(id, name) values(10002, 'JDBC in 10 steps');
insert into course(id, name) values(10003, 'JPQL in 50 steps');
				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

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

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

}

				
			

EntityManager – An interface to the PersistentContext. PersistentContext keeps tracks of the entities which are changed during transactions.

@Transactional – For making any changes to the database, this annotation needs to be enabled.

  • em.persist(Object) – saves the data, after this all the transactions are automatically saved and stored in the database
  • em.flush() – sends the data to the database
  • em.refresh() – get the data from the database
  • em.clear() – Clears the memory cache of EntityManager
  • em.detach(Object) – Detaches the object from the EntityManager cache memory and changes to the entity are no longer tracked.
				
					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() {
		logger.info("playWithEntityManager - start");
		Course course1 = new Course("AngularJs in 100 Steps");
		em.persist(course1);
		em.flush();

		course1.setName("AngularJs in 100 Steps -- updated");
		em.refresh(course1);
		Course course2 = new Course("Microservices in 50 Steps");
		em.persist(course2);
		em.flush();
		course2.setName("Microservices in 50 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 {
		// TODO Auto-generated method stub
		logger.info("FindById 10001: -> {}", courseRepository.findById(10001L));
		courseRepository.playWithEntityManager();
	}

}

				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class JpaHibernateDemoApplicationTests {

	@Test
	void contextLoads() {
	}

}

				
			

@SpringBootTest(classes = JpaHibernateDemoApplication.class)

This will load the SpringContext of the defined class on application startup.  Run this file as “JUnit Test”.

@DirtiesContext – Resets the database for other methods

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

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;

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

@SpringBootTest(classes = JpaHibernateDemoApplication.class)
class CourseRepositoryTest {

	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@Autowired
	CourseRepository courseRepository;
	
	@Test
	void find_by_id() {
		Course course = courseRepository.findById(10001L);
		assertEquals("JPA in 5 steps", course.getName());
	}
	
	@Test
	@DirtiesContext
	void delete_by_id() {
		courseRepository.deleteById(10002L);
		assertNull(courseRepository.findById(10002L));
	}
	
	@Test
	@DirtiesContext
	void save() {
		// getById 10001
		Course course = courseRepository.findById(10001L);
		assertEquals("JPA in 5 steps", course.getName());
		
		// update course
		course.setName("JPA in 5 steps -- updated");
		courseRepository.save(course);
		// check the value
		assertEquals("JPA in 5 steps -- updated", courseRepository.findById(10001L).getName());
		
		// insert new course
		Course course1 = courseRepository.save(new Course("JPA_JDBC"));
		// check the value
		assertEquals("JPA_JDBC", course1.getName());
	}

}

				
			

JPQL – we use entities to make queries.

				
					package com.brains.jpa.hibernate.jpahibernatedemo;

import java.util.List;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

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

import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import jakarta.persistence.TypedQuery;

@SpringBootTest(classes = JpaHibernateDemoApplication.class)
class JPQLTest {

	@Autowired
	EntityManager em;
	
	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@Test
	void jpql_basic() {
		Query query = em.createQuery("select c from Course c");
		List resultList = query.getResultList();
		logger.info("list: -> {}", resultList);
	}
	
	// List of fixed type
	@Test
	void jpql_typed() {
		TypedQuery<Course> query = em.createQuery("select c from Course c", Course.class);
		List resultList = query.getResultList();
		logger.info("Course: -> {}", resultList);
	}

    // Query with where clause
	@Test
	void jpql_where() {
		TypedQuery<Course> query = em.createQuery("select c from Course c where name like '%10%'", Course.class);
		List resultList = query.getResultList();
		logger.info("Where Course: -> {}", resultList);
	}

}

				
			

Related Tutorials

JUnit

JUnit is a testing library used to test the System Integration(full system after deployment) and Unit testing(Class or method testing).

@BeforeAll and @AfterAll requires the methods to be of type static.

				
					package com.brains.junit;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class MyBeforeAfterTest {

	@BeforeAll
	static void beforeAll() {
		System.out.println("beforeAll");
	}
	
	@BeforeEach
	void beforeEach() {
		System.out.println("beforeEach");
	}
	
	@Test
	void test1() {
		System.out.println("test1");
	}
	
	@Test
	void test2() {
		System.out.println("test2");
	}
	
	@Test
	void test3() {
		System.out.println("test3");
	}

	@AfterEach
	void afterEach() {
		System.out.println("afterEach");
	}
	
	@AfterAll
	static void afterAll() {
		System.out.println("afterAll");
	}
}

				
			

Console output

				
					beforeAll
beforeEach
test1
afterEach
beforeEach
test2
afterEach
beforeEach
test3
afterEach
afterAll

				
			

Create MyMath.java class and add a calculate method, we would be applying the test cases on this method.

				
					package com.brains.junit;

public class MyMath {

	public int calculateSum(int[] numbers) {
		int total = 0;
		for(int i:numbers) {
			total += i;
		}
		return total;
	}
}

				
			

Create the Test class in a separate package and use the existing Classes to test it’s functionality.

				
					package com.brains.junit;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.Arrays;
import java.util.List;

import org.junit.jupiter.api.Test;

class MyMathTest {

	private MyMath math = new MyMath();
	List<String> todos = Arrays.asList("AWS", "DevOps", "Java");

	// test sum {1,2,3} -> 6
	@Test
	void calculate_sum_three_member() {
		assertEquals(6, math.calculateSum(new int[] {1,2,3}));
	}
	
	// test sum {} -> 0
	@Test
	void calculate_sum_zero_lengthArray() {
		assertEquals(0, math.calculateSum(new int[] {}));
	}
	
	@Test
	void test() {
		assertEquals(true, todos.contains("AWS"));
		assertEquals(3, todos.size());
	}

}

				
			

On running the JUnit Test case, if all the tests are passed, it would show green bar as shown below.

 

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

Using Dynamic Resource Properties

If you want to use dynamic properties in your spring applications then it is best to make a class and use @ConfigurationProperties.

 

application.properties

				
					logging.level.org.springframework=debug
#spring.profiles.active=dev

currency-service.url=http://brains.com
currency-service.username=defaultUsername
currency-service.key=defaultKey
				
			
				
					package com.brains.springboot.learnspringboot;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import lombok.Data;

@ConfigurationProperties(prefix = "currency-service")
@Data
@Component
public class CurrencyServiceConfiguration {

	private String url;
	private String username;
	private String key;
}

				
			
				
					package com.brains.springboot.learnspringboot;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CourseController {

	@Autowired
	private CurrencyServiceConfiguration currencyServiceConfiguration;

	@GetMapping("/currency")
	public CurrencyServiceConfiguration getCurrencyDetails() {
		return currencyServiceConfiguration;
	}
}

				
			

Running the Application

 

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

Spring In-Memory Authentication – Dynamic users

This illustrates the use of Spring in-memory authentication. We also look into how to customize the Spring Security AuthenticationManager to use Spring Security in-memory authentication and add multiple users with different attributes, authorities, and roles.
Let’s use Spring boot to quickly create and bootstrap spring applications. We configure Spring Security to use In-Memory Authentication in this spring boot application.

Tools and Technologies Used

  • Spring Boot – 2.7.8
  • Spring Framework – 5.7.6
  • Spring Security – 5.7.6
  • Maven – 3.8.7
  • Spring Tool Suite IDE – 4.14.0.RELEASE
  • OS – Linux 5.15.93-1-MANJARO

Development Steps

Let’s use below development steps to create this example:
  1. Creating a Spring Boot Application
  2. Project Structure
  3. Maven Dependencies – Pom.xml
  4. Spring Security In-Memory Authentication
  5. Running the Application
  6. Demo
  7. Conclusion

1. Creating a Spring Boot Application

There are many ways to create a Spring Boot application. You can refer below articles to create a Spring Boot application.
>> Create Spring Boot Project With Spring Initializer
>> Create Spring Boot Project in Spring Tool Suite [STS]

2. Project Structure

Following is the package or project structure for your reference 
 
 
 

3. Maven Dependencies – Pom.xml

Make sure the following dependencies reside on the class-path.
				
					<?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>in-memory</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>in-memory</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.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>

				
			

4. Spring Security In-Memory Authentication

In the following configuration class, we are using the AuthenticationManagerBuilder with the In-memory UserDetailsManagerConfigurer to configure the Spring Security In-Memory Authentication.
				
					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.provisioning.InMemoryUserDetailsManager;

@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");
	}
	
	@Bean
	public InMemoryUserDetailsManager getInMemoryUserDetailsManager() {
		return new InMemoryUserDetailsManager();
	}
}

				
			
Notice that we are using a builder pattern to create multiple users with different attributes, authorities, and roles. This automatically configures a UserDetailsService which we can use.
Note that we have added a password storage format, for plain text, add {noop}. Prior to Spring Security 5.0, the default PasswordEncoder was NoOpPasswordEncoder which required plain text passwords. In Spring Security 5, the default is DelegatingPasswordEncoder, which required Password Storage Format like {noop}.
 

5. Simple Rest Web Service

Let’s create a simple rest service that is protected. We can obtain the current in-memory user by injecting the Authentication as an argument of the method
				
					package com.example.demo.controller;

import org.springframework.context.annotation.Bean;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
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;

@RestController
public class WelcomeController {

	private final InMemoryUserDetailsManager inMemoryUserDetailsManager;
	
	public WelcomeController(InMemoryUserDetailsManager inMemoryUserDetailsManager) {
		this.inMemoryUserDetailsManager = inMemoryUserDetailsManager; 
	}
	
	@GetMapping("/")
	public String greeting() {
		return "Welcome: home";
	}
	@GetMapping("/login")
	public String greeting(Authentication auth) {
		UserDetails user = inMemoryUserDetailsManager.loadUserByUsername(auth.getName());
		System.out.println(user);
		return "Welcome: "+auth.toString();
	}
	
	@PostMapping("/register")
	public String register(@RequestParam("username") String username, @RequestParam("password") String password) {
		inMemoryUserDetailsManager.createUser(User
				.withUsername(username)
				.password("{noop}"+password)
				.roles("USER")
				.build()
				);
		return username +" Created!";
	}
	
	@Bean
	public void addUsersOnStartup() {
		UserDetails user = User.builder()
			.username("user")
			.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
			.roles("USER")
			.build();
		UserDetails admin = User.builder()
			.username("admin")
			.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
			.roles("USER", "ADMIN")
			.build();
		inMemoryUserDetailsManager.createUser(user);
		inMemoryUserDetailsManager.createUser(admin);
	}
}

				
			

6. Running the Application

Let’s run the spring boot application with following entry point:
				
					package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class InMemoryApplication {

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

				
			

7. Demo

Hit this link in browser – http://localhost:8080/. Below is the default login page provided by spring security. 
 
Register new users via the Post API
Login with the newly created users
 

Related Tutorials

Spring Security In-Memory Authentication using @Bean

This illustrates the use of Spring in-memory authentication. We also look into how to customize the Spring Security AuthenticationManager to use Spring Security in-memory authentication and add multiple users with different attributes, authorities, and roles.
Let’s use Spring boot to quickly create and bootstrap spring applications. We configure Spring Security to use In-Memory Authentication in this spring boot application.

Tools and Technologies Used

  • Spring Boot – 2.7.8
  • Spring Framework – 5.7.6
  • Spring Security – 5.7.6
  • Maven – 3.8.7
  • Spring Tool Suite IDE – 4.14.0.RELEASE
  • OS – Linux 5.15.93-1-MANJARO

Development Steps

Let’s use below development steps to create this example:
  1. Creating a Spring Boot Application
  2. Project Structure
  3. Maven Dependencies – Pom.xml
  4. Spring Security In-Memory Authentication
  5. Running the Application
  6. Demo
  7. Conclusion

1. Creating a Spring Boot Application

There are many ways to create a Spring Boot application. You can refer below articles to create a Spring Boot application.
>> Create Spring Boot Project With Spring Initializer
>> Create Spring Boot Project in Spring Tool Suite [STS]

2. Project Structure

Following is the package or project structure for your reference 
 
 
 

3. Maven Dependencies – Pom.xml

Make sure the following dependencies reside on the class-path.
				
					<?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>in-memory</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>in-memory</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.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>

				
			

4. Spring Security In-Memory Authentication

In the following configuration class, we are using the AuthenticationManagerBuilder with the In-memory UserDetailsManagerConfigurer to configure the Spring Security In-Memory Authentication.
				
					package com.example.demo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@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();
	}
}

				
			
Notice that we are using a builder pattern to create multiple users with different attributes, authorities, and roles. This automatically configures a UserDetailsService which we can use.
Note that we have added a password storage format, for plain text, add {noop}. Prior to Spring Security 5.0, the default PasswordEncoder was NoOpPasswordEncoder which required plain text passwords. In Spring Security 5, the default is DelegatingPasswordEncoder, which required Password Storage Format like {noop}.
 

5. Simple Rest Web Service

Let’s create a simple rest service that is protected. We can obtain the current in-memory user by injecting the Authentication as an argument of the method.
In the following sample, we use Spring Boot CLI to encode a password value of password and get the encoded password of {bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW:
				
					package com.example.demo.controller;

import org.springframework.context.annotation.Bean;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class WelcomeController {

	@GetMapping("/")
	public String greeting() {
		return "Welcome: home";
	}
	@GetMapping("/login")
	public String greeting(Authentication auth) {
		UserDetails user = users().loadUserByUsername(auth.getName());
		System.out.println(user);
		return "Welcome: "+auth.toString();
	}
	
	@Bean
	public UserDetailsService users() {
		UserDetails user = User.builder()
			.username("user")
			.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
			.roles("USER")
			.build();
		UserDetails admin = User.builder()
			.username("admin")
			.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
			.roles("USER", "ADMIN")
			.build();
		return new InMemoryUserDetailsManager(user, admin);
	}
}

				
			

6. Running the Application

Let’s run the spring boot application with following entry point:
				
					package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class InMemoryApplication {

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

				
			

7. Demo

Hit this link in browser – http://localhost:8080. Below is the default login page provided by spring security. You can create your own custom login page here.

 
 

Related Tutorials