Using Comparators to sort objects in Java

Comparator interface is used to order the objects of user-defined classes. A comparator object is capable of comparing two objects of two different classes. Following comparator will sort by ID

Example

package collections;

import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

class Student {
	int ID;
	String name;
	char grade;

	public Student(int iD, String name, char grade) {
		super();
		ID = iD;
		this.name = name;
		this.grade = grade;
	}

	@Override
	public String toString() {
		return "Student [ID=" + ID + ", name=" + name + ", grade=" + grade + "]";
	}
}


class IdComparator implements Comparator<Student> {

    @Override
    public int compare(Student o1, Student o2) {
    	int result = o1.ID - o2.ID; // Sorting by ID
    	return result;
    }
}

public class StudentSort {

	public static void main(String[] args) {

		List<Student> stu = new ArrayList<>();
		stu.add(new Student(10, "General Motors", 'A'));
		stu.add(new Student(100, "Ferrari", 'B'));
		stu.add(new Student(5, "Mustang", 'C'));
		stu.add(new Student(5, "Ford", 'F'));

		System.out.println("Before");
		printCollections(stu);
		Collections.sort(stu, new IdComparator());
		System.out.println("After");
		printCollections(stu);
	}

	private static void printCollections(List<Student> stu) {
		for (Student s : stu)
			System.out.println(s);
	}
}

Output

Before
Student [ID=10, name=General Motors, grade=A]
Student [ID=100, name=Ferrari, grade=B]
Student [ID=5, name=Mustang, grade=C]
Student [ID=5, name=Ford, grade=F]
After
Student [ID=5, name=Mustang, grade=C]
Student [ID=5, name=Ford, grade=F]
Student [ID=10, name=General Motors, grade=A]
Student [ID=100, name=Ferrari, grade=B]

You can define multiple comparators to sort object for different parameters

Example

package collections;

import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

class Student {
	int ID;
	String name;
	char grade;

	public Student(int iD, String name, char grade) {
		super();
		ID = iD;
		this.name = name;
		this.grade = grade;
	}

	@Override
	public String toString() {
		return "Student [ID=" + ID + ", name=" + name + ", grade=" + grade + "]";
	}
}

class IdComparator implements Comparator<Student> {

	@Override
	public int compare(Student o1, Student o2) {
		int result = o1.ID - o2.ID; // Sorting by ID
		return result;
	}
}

class NameComparator implements Comparator<Student> {

	@Override
	public int compare(Student o1, Student o2) {
		int result = o1.name.compareTo(o2.name); // Sorting by name
		return result;
	}
}

public class StudentSort {

	public static void main(String[] args) {

		List<Student> stu = new ArrayList<>();
		stu.add(new Student(10, "General Motors", 'A'));
		stu.add(new Student(100, "Ferrari", 'B'));
		stu.add(new Student(5, "Mustang", 'C'));
		stu.add(new Student(5, "Ford", 'F'));

		System.out.println("Original List");
		printCollections(stu);
		Collections.sort(stu, new IdComparator()); // comparing by ID
		System.out.println("After Comparing by ID");
		printCollections(stu);
		Collections.sort(stu, new NameComparator()); // comparing by Name
		System.out.println("After Comparing by Name");
		printCollections(stu);
	}

	private static void printCollections(List<Student> stu) {
		for (Student s : stu)
			System.out.println(s);
	}
}

Output

Original List
Student [ID=10, name=General Motors, grade=A]
Student [ID=100, name=Ferrari, grade=B]
Student [ID=5, name=Mustang, grade=C]
Student [ID=5, name=Ford, grade=F]
After Comparing by ID
Student [ID=5, name=Mustang, grade=C]
Student [ID=5, name=Ford, grade=F]
Student [ID=10, name=General Motors, grade=A]
Student [ID=100, name=Ferrari, grade=B]
After Comparing by Name
Student [ID=100, name=Ferrari, grade=B]
Student [ID=5, name=Ford, grade=F]
Student [ID=10, name=General Motors, grade=A]
Student [ID=5, name=Mustang, grade=C]

Use comparable to sort first by 1st field and then second field in Java

In order to sort first by ID and if the Id’s are same then sort by name. Please follow the code

Example

package collections;

import java.util.List;
import java.util.ArrayList;
import java.util.Collections;

class Student implements Comparable<Student> {
	int ID;
	String name;
	char grade;

	public Student(int iD, String name, char grade) {
		super();
		ID = iD;
		this.name = name;
		this.grade = grade;
	}

	@Override
	public String toString() {
		return "Student [ID=" + ID + ", name=" + name + ", grade=" + grade + "]";
	}

	@Override
	public int compareTo(Student o) {
		int result = this.ID - o.ID; // Sorting by ID

		if (result == 0) { // if both the integer values are same, then sort by name
			result = this.name.compareTo(o.name); // comparing strings
		}

		return result;
	}
}

public class StudentSort {

	public static void main(String[] args) {

		List<Student> stu = new ArrayList<>();
		stu.add(new Student(10, "General Motors", 'A'));
		stu.add(new Student(100, "Ferrari", 'B'));
		stu.add(new Student(5, "Mustang", 'C'));
		stu.add(new Student(5, "Ford", 'F'));

		System.out.println("Before");
		printCollections(stu);
		Collections.sort(stu);
		System.out.println("After");
		printCollections(stu);
	}

	private static void printCollections(List<Student> stu) {
		for (Student s : stu)
			System.out.println(s);
	}
}

Output

Before
Student [ID=10, name=General Motors, grade=A]
Student [ID=100, name=Ferrari, grade=B]
Student [ID=5, name=Mustang, grade=C]
Student [ID=5, name=Ford, grade=F]
After
Student [ID=5, name=Ford, grade=F]
Student [ID=5, name=Mustang, grade=C]
Student [ID=10, name=General Motors, grade=A]
Student [ID=100, name=Ferrari, grade=B]

Builder Design Pattern in Java

Builder pattern was introduced to solve some of the problems with Factory and Abstract Factory design patterns when the Object contains a lot of attributes.

There are three major issues with Factory and Abstract Factory design patterns when the Object contains a lot of attributes.

  1. Too Many arguments to pass from client program to the Factory class that can be error prone because most of the time, the type of arguments are same and from client side its hard to maintain the order of the argument.
  2. Some of the parameters might be optional but in Factory pattern, we are forced to send all the parameters and optional parameters need to send as NULL.
  3. If the object is heavy and its creation is complex, then all that complexity will be part of Factory classes that is confusing.

Example – Entity Class (Phone.java)

class Phone {
	protected String model;
	protected int frontCamera;
	protected int backCamera;
	protected int battery;
	protected String ram;
	protected float price;
	protected int storage;

	@Override
	public String toString() {
		return "Phone [model=" + model + ", frontCamera=" + frontCamera + ", backCamera=" + backCamera + ", battery="
				+ battery + ", ram=" + ram + ", price=" + price + ", storage=" + storage + "]";
	}
}

Builder Helper Class (PhoneBuilder.java)

class PhoneBuilder extends Phone {

	public PhoneBuilder setModel(String model) {
		this.model = model;
		return this;
	}

	public PhoneBuilder setFrontCamera(int frontCamera) {
		this.frontCamera = frontCamera;
		return this;
	}

	public PhoneBuilder setBackCamera(int backCamera) {
		this.backCamera = backCamera;
		return this;
	}

	public PhoneBuilder setBattery(int battery) {
		this.battery = battery;
		return this;
	}

	public PhoneBuilder setRam(String ram) {
		this.ram = ram;
		return this;
	}

	public PhoneBuilder setPrice(float price) {
		this.price = price;
		return this;
	}

	public PhoneBuilder setStorage(int storage) {
		this.storage = storage;
		return this;
	}

	public PhoneBuilder build() {
		return this;
	}
}

Main Method class – object creation (BuilderPattern)

import java.util.HashMap;
import java.util.Map;

public class BuilderPattern {

	public static void main(String[] args) {
		
		// With constructors all the parameters are required, if default is to be supplied, then we have to mention each 
		// time those default values
		/*
		 * Phone nokia1100 = new Phone("Nokia 1100", 13, 20, 4000, "2 GB", 1100.00f, 4);
		 * Phone nokia1110 = new Phone("Nokia 1100", 13, 20, 4000, "2 GB", 1100.00f, 4);
		 */

		Phone ph1 = new PhoneBuilder().setModel("Nokia 1100").build();
		System.out.println(ph1.toString());

		Phone ph2 = new PhoneBuilder().setModel("Nokia 1110").setBattery(4000).build();
		System.out.println(ph2.toString());

		Phone ph3 = new PhoneBuilder().setModel("Nokia 1600").setBattery(4000).setFrontCamera(13).setBackCamera(20)
				.build();
		System.out.println(ph3.toString());

		HashMap<String, Map> hash_map = new HashMap<String, Map>();
		Map<String, Integer> hm = new HashMap<String, Integer>();

		hm.put("field1", 10);
		hash_map.put("0", hm);
		
		hm.put("field2", 20);
		hash_map.put("1", hm);

	}
}

Shallow vs Deep copy in Java

When creating copies of arrays or objects one can make a deep copy or a shallow copy.

Shallow Copy

import java.util.Arrays;

public class ShallowAndDeep2 {

	public static void main(String[] args) {
		
		int[] arr1 = { 1, 2, 3, 4, 5, 6 };		
		
		// Printing array elements
		System.out.println("arr1 : " + Arrays.toString(arr1));
		int[] arr2 = arr1; // Shallow Copy, both arrays points to same memory locations
		
		// one value of array2 changed, but while both array points to same location, values of arr1 are also same
		arr2[2] = 10; 

		System.out.println("arr2 : " + Arrays.toString(arr2));
		System.out.println("arr1 again : " + Arrays.toString(arr1));
	}
}

Output

arr1 : [1, 2, 3, 4, 5, 6]
arr2 : [1, 2, 10, 4, 5, 6]
arr1 again : [1, 2, 10, 4, 5, 6]

In above example we have changed the value of arr1[2] to 10. Since it’s a shallow copy so corresponding arr1 values are also changed.

Deep Copy

import java.util.Arrays;

public class ShallowAndDeep2 {

	public static void main(String[] args) {

		int[] arr1 = { 1, 2, 3, 4, 5, 6 };

		// Printing array elements
		System.out.println("arr1 : " + Arrays.toString(arr1));
		int[] arr2 = new int[arr1.length];

		// Deep copy, copying elements one by one 
		for (int a = 0; a < arr1.length; a++)
			arr2[a] = arr1[a];

		// one value of array2 changed, and only value for arr1 will be changed
		// values of arr1 and arr2 are not same
		arr2[2] = 10;

		System.out.println("arr2 : " + Arrays.toString(arr2));
		System.out.println("arr1 again : " + Arrays.toString(arr1));
	}
}

Output

arr1 : [1, 2, 3, 4, 5, 6]
arr2 : [1, 2, 10, 4, 5, 6]
arr1 again : [1, 2, 3, 4, 5, 6]

Efficiently removing elements while iterating a collection in Java

One of the common problem while removing elements from an ArrayList in Java is the ConcurrentModificationException. If you use classical for loop with the index or enhanced for loop and try to remove an element from the ArrayList using remove() method, you will get the ConcurrentModificationException but if you use Iterator’s remove method or ListIterator’s remove() method, then you won’t get this error and be able to remove the element. It’s an unwritten rule in Java that while looping through the list, you should not add() or remove() elements until the collection supports fail-safe Iterator e.g. CopyOnWriteArrayList, which operate on a copy of list rather than the original list.

Example – Wrong way to delete

import java.util.ArrayList;
import java.util.Iterator;

public class SafelyRemoveFromCollections {

	public static void main(String[] args) {

		ArrayList<Integer> lst = new ArrayList<Integer>();
		lst.add(11);
		lst.add(12);
		lst.add(13);
		lst.add(14);

		for (Integer iin : lst) {
			if (iin == 12) {
				lst.remove(iin); // will throw exception
			}
		}
	}
}

Output

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
	at java.util.ArrayList$Itr.next(Unknown Source)
	at collections.SafelyRemoveFromCollections.main(SafelyRemoveFromCollections.java:17)

Correct way to delete (using iterators)


package collections;

import java.util.ArrayList;
import java.util.Iterator;

public class SafelyRemoveFromCollections {

	public static void main(String[] args) {

		ArrayList<Integer> lst = new ArrayList<Integer>();
		lst.add(11);
		lst.add(12);
		lst.add(13);
		lst.add(14);

		Iterator<Integer> iter = lst.iterator();

		System.out.println("-------Before deleting---------");
		display(lst);

		while (iter.hasNext()) {
			if (iter.next() == 12) {
				iter.remove();
			}
		}

		System.out.println("-------After deleting---------");
		display(lst);
	}

	private static void display(ArrayList<Integer> lst) {
		for (Integer a : lst)
			System.out.println(a);

	}

}

Output

-------Before deleting---------
11
12
13
14
-------After deleting---------
11
13
14

Spring Boot CRUD REST API with MySql

Here is an example of the REST API using spring boot with MySql as the database. We have used a single table of Employee, with custom exception handler to give users readable error messages.

Directory structure

Model Class (employee.java)

package com.loopandbreak.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;

@Entity
@Table(name = "employees")
public class Employee {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private long id;

	@Column(name = "first_name")
	@NotNull
	private String firstName;

	@Column(name = "last_name")
	@NotNull
	private String lastName;

	@Column(name = "email")
	@NotNull
	@Email
	private String email;

	public Employee() {
	}

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}
}

Employee Repo (EmployeeRepository.java)

package com.loopandbreak.repos;

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

import com.loopandbreak.model.Employee;

public interface EmployeeRepository extends JpaRepository<Employee, Long> {
	Employee findByEmail(String email);
}

Employee Service interface (EmployeeService.java)

package com.loopandbreak.services;

import java.util.List;
import java.util.Optional;

import com.loopandbreak.model.Employee;

public interface EmployeeService {

	List<Employee> getAllEmployees();

	Optional<Employee> getEmployeeById(Long employeeId);

	void addEmployee(Employee employee);
	
	Employee getEmployeeByEmail(String email);

	void deleteEmployee(Long idOfEmployee);

}

Employee Service interface (EmployeeServiceImpl.java)

package com.loopandbreak.servicesimpl;

import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.loopandbreak.model.Employee;
import com.loopandbreak.repos.EmployeeRepository;
import com.loopandbreak.services.EmployeeService;

@Service
public class EmployeeServiceImpl implements EmployeeService {

	@Autowired
	private EmployeeRepository employeeRepository;

	@Override
	public List<Employee> getAllEmployees() {
		return employeeRepository.findAll();
	}

	@Override
	public Optional<Employee> getEmployeeById(Long employeeId) {
		return employeeRepository.findById(employeeId);
	}

	@Override
	public void addEmployee(Employee employee) {
		employeeRepository.save(employee);
	}

	@Override
	public Employee getEmployeeByEmail(String email) {
		Employee employeeExists = employeeRepository.findByEmail(email);		
		return employeeExists;
	}

	@Override
	public void deleteEmployee(Long idOfEmployee) {
		employeeRepository.deleteById(idOfEmployee);
	}
}

Employee Controller (EmployeeController.java)

package com.loopandbreak.controllers;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.loopandbreak.exceptions.EmployeeAlreadyExists;
import com.loopandbreak.exceptions.EmployeeNotFoundException;
import com.loopandbreak.model.Employee;
import com.loopandbreak.servicesimpl.EmployeeServiceImpl;

@RestController
@RequestMapping("/api/v1")
public class EmployeeController {

	@Autowired
	private EmployeeServiceImpl employeeServiceImpl;

	@GetMapping("/employees")
	public ResponseEntity<List<Employee>> listEmployees() {
		List<Employee> employees = employeeServiceImpl.getAllEmployees();

		if (employees.isEmpty()) {
			return new ResponseEntity<List<Employee>>(HttpStatus.NO_CONTENT);
		}

		return new ResponseEntity<List<Employee>>(employees, HttpStatus.OK);
	}

	@GetMapping("/employees/{id}")
	public ResponseEntity<Employee> getEmployeeById(@PathVariable(value = "id") final Long employeeId) {
		Optional<Employee> employee = employeeServiceImpl.getEmployeeById(employeeId);

		if (!employee.isPresent()) {
			return new ResponseEntity<Employee>(HttpStatus.NOT_FOUND);
		}
		return new ResponseEntity<Employee>(employee.get(), HttpStatus.OK);

	}

	@PostMapping(value = "/employees", consumes = MediaType.APPLICATION_JSON_VALUE)
	public ResponseEntity<Employee> createUser(@Valid @RequestBody final Employee employee)
			throws EmployeeAlreadyExists {

		Employee ifEmployeeExists = employeeServiceImpl.getEmployeeByEmail(employee.getEmail());

		if (ifEmployeeExists != null) {
			throw new EmployeeAlreadyExists("A user with email already exists");
		}

		employeeServiceImpl.addEmployee(employee);
		return new ResponseEntity<Employee>(employee, HttpStatus.CREATED);
	}

	@PutMapping(value = "/employees/{id}", consumes = MediaType.APPLICATION_JSON_VALUE)
	public ResponseEntity<Employee> updateUser(@PathVariable("id") final Long id,
			@Valid @RequestBody final Employee employee) throws EmployeeNotFoundException {

		Optional<Employee> emp = employeeServiceImpl.getEmployeeById(id);

		if (!emp.isPresent()) {
			throw new EmployeeNotFoundException("No employee found for name " + employee.getFirstName());
		}

		emp.get().setFirstName(employee.getFirstName());
		emp.get().setLastName(employee.getLastName());
		emp.get().setEmail(employee.getEmail());

		employeeServiceImpl.addEmployee(emp.get());

		return new ResponseEntity<Employee>(HttpStatus.ACCEPTED);
	}

	@DeleteMapping("/employees/{id}")
	public Map<String, Boolean> deleteEmployee(@PathVariable(value = "id") Long employeeId)
			throws EmployeeNotFoundException {
		Optional<Employee> emp = employeeServiceImpl.getEmployeeById(employeeId);

		if (!emp.isPresent()) {
			throw new EmployeeNotFoundException("Employee not found for employee Id " + employeeId);
		}

		employeeServiceImpl.deleteEmployee(employeeId);
		Map<String, Boolean> response = new HashMap<String, Boolean>();

		response.put("deleted", Boolean.TRUE);
		return response;
	}
}

Exception handlers

package com.loopandbreak.exceptions;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value = HttpStatus.CONFLICT)
public class EmployeeAlreadyExists extends Exception {

	private static final long serialVersionUID = 1L;

	public EmployeeAlreadyExists(String exceptionContent) {
		super(exceptionContent);
	}

}
package com.loopandbreak.exceptions;

public class EmployeeNotFoundException extends Exception {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	public EmployeeNotFoundException(String message)
	{
		super(message);
	}

}
package com.loopandbreak.exceptions;

import java.util.Date;

public class ErrorDetails {

	private Date timestamp;
	private String message;
	private String details;

	public ErrorDetails(Date timestamp, String message, String details) {
		this.timestamp = timestamp;
		this.message = message;
		this.details = details;
	}

	public Date getTimestamp() {
		return timestamp;
	}

	public String getMessage() {
		return message;
	}

	public String getDetails() {
		return details;
	}

}
package com.loopandbreak.exceptions;

import java.util.Date;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler{
	
	@ExceptionHandler(EmployeeAlreadyExists.class)
	public ResponseEntity<?> exployeeAlreadyExistsException(EmployeeAlreadyExists alreadyExists, WebRequest request)
	{
		ErrorDetails errorDetails = new ErrorDetails(new Date(), alreadyExists.getMessage(), request.getDescription(false));
		return new ResponseEntity<>(errorDetails, HttpStatus.CONFLICT);		
	}
		
	@ExceptionHandler(EmployeeNotFoundException.class)
	public ResponseEntity<?> exployeeNotFoundException(EmployeeNotFoundException employeeNotFoundException, WebRequest request)
	{
		ErrorDetails errorDetails = new ErrorDetails(new Date(), employeeNotFoundException.getMessage(), request.getDescription(false));
		return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);		
	}
	
}

Main Application Class

package com.loopandbreak;

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

@SpringBootApplication
public class EmployeeApplication {

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

Inserting record into DB

Getting all records

Selecting single record

Updating Record

Use your class as a bean in Spring Boot

Depending on your needs, you can either leverage @ComponentScan to automatically detect your class and have an instance created, use it together with @Autowired and @Value to get dependencies or properties injected, or you can use a method annotated with @Bean to have more control over the construction of the bean being created

Spring project main class

package com.example.demo;

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

@SpringBootApplication
public class DemoApplication {

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

Using @Component

package com.example.demo;

import javax.annotation.PostConstruct;

import org.springframework.stereotype.Component;

@Component
public class HelloBean {

	@PostConstruct
	public void sayHello() {
		System.out.println("Hello world, from spring bean");
	}
}

Spring Boot will detect this class and create a bean instance from it. The @PostConstruct annotated method is invoked after construction and injection of all dependencies.

Output

Hello world, from spring bean

Sort List of Collections in Java using Comparable interface

A comparable object is capable of comparing itself with another object. The class itself must implements the java.lang.Comparable interface to compare its instances.

Example

package collections;

import java.util.List;
import java.util.ArrayList;
import java.util.Collections;

class Student implements Comparable<Student> {
	int ID;
	String name;
	char grade;

	public Student(int iD, String name, char grade) {
		super();
		ID = iD;
		this.name = name;
		this.grade = grade;
	}

	@Override
	public String toString() {
		return "Student [ID=" + ID + ", name=" + name + ", grade=" + grade + "]";
	}

	@Override
	public int compareTo(Student o) {
		int result = this.ID - o.ID; // Sorting by ID
		return result;
	}
}

public class StudentSort {

	public static void main(String[] args) {

		List<Student> stu = new ArrayList<>();
		stu.add(new Student(10, "General Motors", 'A'));
		stu.add(new Student(100, "Ferrari", 'B'));
		stu.add(new Student(5, "Mustang", 'C'));
		stu.add(new Student(5, "Ford", 'F'));

		System.out.println("Before");
		printCollections(stu);
		Collections.sort(stu);
		System.out.println("After");
		printCollections(stu);
	}

	private static void printCollections(List<Student> stu) {
		for (Student s : stu)
			System.out.println(s);
	}
}

Output

Before
Student [ID=10, name=General Motors, grade=A]
Student [ID=100, name=Ferrari, grade=B]
Student [ID=5, name=Mustang, grade=C]
Student [ID=5, name=Ford, grade=F]
After
Student [ID=5, name=Mustang, grade=C]
Student [ID=5, name=Ford, grade=F]
Student [ID=10, name=General Motors, grade=A]
Student [ID=100, name=Ferrari, grade=B]

In the above example, ID 5 is same for Mustang and Ford, if we want to sort first by ID and if the ID’s are same same then sort by name. Then we have to modify our compareTo method. Code is explained in next article Sort by first and then by second

Iterate around an ArrayList using Iterator

Example below shows how to iterate around an ArrayList.

Example

public class IteratingArrayList {
	public static void main(String[] args) {
		List<Integer> arrayList = new ArrayList<Integer>();

		arrayList.add(100);
		arrayList.add(101);
		arrayList.add(102);

		Iterator<Integer> arraylistIterator = arrayList.iterator();

		while (arraylistIterator.hasNext()) {
			Integer number = arraylistIterator.next();
			System.out.println(number);
		}
	}
}

Output

100
101
102

What is the use of equals method in Java?

Equals method is used when we compare two objects. Default implementation of equals method is
defined in Object class. The implementation is similar to == operator. Two object references are equal
only if they are pointing to the same object.
Consider the following example

class Student {
	private int marks;

	public int getMarks() {
		return marks;
	}

	public void setMarks(int var) {
		this.marks = var;
	}
}

public class TestClass {

	public static void main(String[] args) {
		Student john = new Student();
		john.setMarks(10);

		Student kevin = new Student();
		kevin.setMarks(10);
		
		//Student peter = kevin;

		if (john.equals(kevin))
			System.out.println("Both objects are equal, compare by default equals method");
		else if (john == kevin)
			System.out.println("Both objects are equal, compare by == ");
		else
			System.out.println("Not equal");
	}
}

In the above example, marks for john and kevin are same but the output will be “Not equal” because here we are comparing only objects instead of comparing their marks. Neither equals nor == are comparing the object values. Now if we modify our code to this :

class Student {
	private int marks;

	public int getMarks() {
		return marks;
	}

	public void setMarks(int var) {
		this.marks = var;
	}
}

public class TestClass {

	public static void main(String[] args) {
		Student john = new Student();
		john.setMarks(10);

		Student kevin = john;
		if (john.equals(kevin))
			System.out.println("Both objects are equal, compare by default equals method");

		if (john == kevin)
			System.out.println("Both objects are equal, compare by == ");
		
	}
}

Output of this code is :

Both objects are equal, compare by default equals method
Both objects are equal, compare by == 

Here both the objects are pointing to each other, so the output will be calculated equal by == and equals as well, but the basic question still remains is “how to compare values within objects”. For achieving this we have to override equals method.

Example

package interviewquestions;

class Student {
	private int marks;

	public int getMarks() {
		return marks;
	}

	public void setMarks(int var) {
		this.marks = var;
	}

	// Overriding equals method
	@Override
	public boolean equals(Object obj) {
		Student st = (Student) obj;
		if (st.getMarks() != this.marks)
			return false;
		return true;
	}
}

public class TestClass {

	public static void main(String[] args) {
		Student john = new Student();
		john.setMarks(10);

		Student kevin = new Student();
		kevin.setMarks(10);

		if (john.equals(kevin))
			System.out.println("Marks are same for both students");
		else
			System.out.println("Marks are not same for both students");
	}
}

Output

Marks are same for both students

Any equals implementation should satisfy these properties:

  1. Reflexive. For any reference value x, x.equals(x) returns true.
  2. Symmetric. For any reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
  3. Transitive. For any reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) must return true.
  4. Consistent. For any reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, if no information used in equals is modified.
  5. For any non-null reference value x, x.equals(null) should return false.
  6. Objects of the same class are used for comparison

Our code will fail for 5 and 6 points, here we are modifying program so it doesn’t break for 5 and 6 points

class Student {
	private int marks;

	public int getMarks() {
		return marks;
	}

	public void setMarks(int var) {
		this.marks = var;
	}

	// Overriding equals method
	@Override
	public boolean equals(Object obj) {

		// to check for null
		if (obj == null) {
			return false;
		}

		// to check for same class
		if (getClass() != obj.getClass())
			return false;
		
		// finally compare object if all goes well
		Student st = (Student) obj;
		if (st.getMarks() != this.marks)
			return false;
		return true;
	}
}

public class TestClass {

	public static void main(String[] args) {

		Student john = new Student();
		john.setMarks(10);

		Student kevin = new Student();
		kevin.setMarks(10);

		if (john.equals(kevin))
			System.out.println("Marks are same for both students");
		else
			System.out.println("Marks are not same for both students");
	}
}

TreeMap in Java

TreeMap is Red-Black tree based NavigableMap implementation. It is sorted according to the natural ordering of its keys.

Example

import java.util.Map;
import java.util.TreeMap;

public class Detective {

	public static void main(String[] args) {

		//The entries in a TreeMap are always sorted based on the natural ordering of the keys
		Map<Integer, String> statements = new TreeMap<Integer, String>();

		// Inserting statements into the map
		statements.put(5, "shouting");
		statements.put(1, "fight");
		statements.put(7, "fleeing");
		statements.put(9, "gunshot");
		statements.put(15, "panic");
		statements.put(3, "anger");
		statements.put(8, "shouting");
		
		// printing the already sorted map
		printStatements(statements);
	}

	public static void printStatements(Map<Integer, String> map) {
		for (Map.Entry<Integer, String> entry : map.entrySet()) {
			System.out.println( "Key : " + entry.getKey() +  " Value : " +entry.getValue());
		}
	}
}

Output

Key : 1 Value : fight
Key : 3 Value : anger
Key : 5 Value : shouting
Key : 7 Value : fleeing
Key : 8 Value : shouting
Key : 9 Value : gunshot
Key : 15 Value : panic

Serialization and Deserialization in Java with Example

Serialization is a mechanism of converting the state of an object into a byte stream. Deserialization is the reverse process where the byte stream is used to recreate the actual Java object in memory. This mechanism is used to persist the object.

We have divided the example in 3 classes :

  1. Employee (class whose object is/are to be serialized/deserialized)
  2. Serializze (to serialize the class and store it to a file on local hard drive)
  3. Deserializze (to deserialize the file and fetch values from the object)

Example (Employee.java)

public class Employee implements java.io.Serializable {
	public int id;
	public String name;
	
	public Employee(int id, String name) {
		super();
		this.id = id;
		this.name = name;
	}
}

Example (Serializze.java)

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Serializze {

	public static void main(String[] args) {
		Employee object = new Employee(1, "John");
		//String filename = "D:" + File.separator + "java" + File.separator + "file";
		String filename = "D:/java/file";

		// Serialization
		try {
			// Saving of object in a file
			FileOutputStream file = new FileOutputStream(filename);
			ObjectOutputStream out = new ObjectOutputStream(file);

			// Method for serialization of object
			out.writeObject(object);

			out.close();
			file.close();

			System.out.println("Object has been serialized");

		}

		catch (IOException ex) {
			System.out.println("IOException is caught");
		}

	}
}

Example (Deserializze.java)

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class Deserializze {

	public static void main(String[] args) {

		Employee employee = null;
		String filename = "D:/java/file";

		// Deserialization
		try {
			// Reading the object from a file
			FileInputStream file = new FileInputStream(filename);
			ObjectInputStream in = new ObjectInputStream(file);

			// typecasting stream of bytes to employee to deserialize
			employee = (Employee) in.readObject();

			in.close();
			file.close();

			System.out.println("Object has been deserialized ");
			System.out.println("a = " + employee.id);
			System.out.println("b = " + employee.name);
		}

		catch (IOException ex) {
			System.out.println("IOException is caught");
		}

		catch (ClassNotFoundException ex) {
			System.out.println("ClassNotFoundException is caught");
		}
	}

}

Output

after running Serializze class
Object has been serialized
after running Deserializze class
Object has been deserialized

a = 1
b = John

What is the purpose of Serialization in Java?

Serialization is simply turning an existing object into a byte array. This byte array represents the class of the object, the version of the object, and the internal state of the object. This byte array can then be used between JVM’s running the same code to transmit/read the object.

Why would we want to do this?

There are several reasons:

  • Communication: If you have two machines that are running the same code, and they need to communicate, an easy way is for one machine to build an object with information that it would like to transmit, and then serialize that object to the other machine. It’s not the best method for communication, but it gets the job done.
  • Persistence: If you want to store the state of a particular operation in a database, it can be easily serialized to a byte array, and stored in the database for later retrieval.
  • Deep Copy: If you need an exact replica of an Object, and don’t want to go to the trouble of writing your own specialized clone() class, simply serializing the object to a byte array, and then de-serializing it to another object achieves this goal.
  • Caching: Really just an application of the above, but sometimes an object takes 10 minutes to build, but would only take 10 seconds to de-serialize. So, rather than hold onto the giant object in memory, just cache it out to a file via serialization, and read it in later when it’s needed.
  • Cross JVM Synchronization: Serialization works across different JVMs that may be running on different architectures.

Tight Coupling and loose coupling

Tight Coupling:

Tightly coupled object is an object that needs to know quite a bit about other objects and are usually highly dependent on each other’s interfaces. Changing one object in a tightly coupled application often requires changes to a number of other objects.

Example

class Plus {
	public int doTheMath(int a, int b) {
		return a + b;
	}
}

class Minus {
	public int doTheMath(int a, int b) {
		return a - b;
	}
}

public class Arithmetic {
	public static void main(String[] args) {
		Plus plus =  new Plus();
		System.out.println(plus.doTheMath(2, 3));
	}
}

In tight Copuling if we have to change the mode of operation from + to – (in this example) we have to change a lot of code, like this

Minus minus = new Minus();
System.out.println(minus.doTheMath(2, 3));

Loose Coupling

Loose coupling is a design goal that seeks to reduce the inter-dependencies between components of a system with the goal of reducing the risk that changes in one component will require changes in any other component.

Example

interface Calculate {
	int doTheMath(int a, int b);
}

class Plus implements Calculate {

	@Override
	public int doTheMath(int a, int b) {
		return a + b;
	}
}

class Minus implements Calculate {

	@Override
	public int doTheMath(int a, int b) {
		return a - b;
	}
}

public class Arithmetic {
	public static void main(String[] args) {
		Calculate calc = new Plus();
		System.out.println(calc.doTheMath(2, 3));
	}
}

In the above example if we have to change the operation from plus to minus we need only one change :

Calculate calc = new Minus();

Just assume your code when you are dealing with 10-20 dependencies in your class. Loosely coupled classes will make changing cases very easy and it’s also more realistic

About Design Patterns

The design patterns are language independent strategies for solving common object-oriented design problems. When you make a design, you should know the names of some common solutions. Learning design patterns is good for people to communicate each other effectively. In fact, you may have been familiar with some design patterns; you may not use well-known names to describe them. SUN suggests GOF (Gang of Four—four pioneer guys who wrote a book named “Design Patterns”- Elements of Reusable Object-Oriented Software). Please make you be familiar with these terms and learn how other people solve the coding problems.

There are three well-known types of design patterns.

  1. Creational Design Patterns: Creational design patterns provide solution to instantiate an object in the best possible way for specific situations. Following design patterns come under this category.
  • Singleton Pattern
  • Factory Pattern
  • Abstract Factory Pattern
  • Builder Pattern
  • Prototype Pattern
  1. Structural Design Patterns: Structural patterns provide different ways to create a class structure, for example using inheritance and composition to create a large object from small objects. Following design patterns come under this category.
  • Adapter Pattern
  • Composite Pattern
  • Proxy Pattern
  • Flyweight Pattern
  • Facade Pattern
  • Bridge Pattern
  • Decorator Pattern
  1. Behavioural Design Patterns: Behavioural patterns provide solution for the better interaction between objects and how to provide lose coupling and flexibility to extend easily. Following design patterns come under this category.
  • Template Method Pattern
  • Mediator Pattern
  • Chain of Responsibility Pattern
  • Observer Pattern
  • Strategy Pattern
  • Command Pattern
  • State Pattern
  • Visitor Pattern
  • Iterator Pattern
  • Interpreter Pattern
  • Memento Pattern

 

Spring Boot CRUD using Thymeleaf MySql

This CRUD example uses a product table, performs crud operations using spring boot and thymeleaf.

Directory Structure

Model class (product.java)

package com.example.demo.product;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

@Entity
public class Product {

	@Id 
	@GeneratedValue(strategy = GenerationType.AUTO) // auto increment ID
	private int id;

	@NotNull // variable should not be null
	@Size(min = 2, max = 30) // variables minimum size is 2 and max is 30
	private String name;

	@NotNull
	@Size(min = 12, max = 50)
	private String description;
	
	private boolean enabled;

	public Product() {
		super();
	}

	public int getId() {
		return id;
	}
	
	public void setId(int id) {
		this.id =  id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public boolean isEnabled() {
		return enabled;
	}

	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}

	@Override
	public String toString() {
		return "ParentCategory [id=" + id + ", name=" + name + ", description=" + description + ", enabled=" + enabled
				+ "]";
	}

}

Table repository (ProductRepo.java)

package com.example.demo.product;

import org.springframework.data.repository.CrudRepository;

public interface ProductRepo extends CrudRepository<Product, Integer> {

}

Controller (ProductController.java)

package com.example.demo.product;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class ProductController {

	@Autowired
	ProductRepo productRepo;

	// Displays dashboard
	@GetMapping("/")
	public String index() {
		return "dashboard";
	}

	// Displays all records
	@GetMapping("/all")
	public String allProducts(Model model) {

		Iterable<Product> allProducts = productRepo.findAll();
		model.addAttribute("allProducts", allProducts);

		return "index";
	}

	// Displays single record
	@GetMapping("/{id}")
	public String showProduct(@PathVariable Integer id, Model model) {

		Product product = productRepo.findOne(id);

		if (null == product) {
			return null;
		} else {
			model.addAttribute("product", product);
			return "view";
		}
	}

	// render empty model for adding new record
	@GetMapping("/add")
	public String newProduct(Model model) {
		Product product = new Product();
		model.addAttribute("product", product);
		return "form";
	}

	// handles post request for adding record and updated record
	@PostMapping("/add")
	public String createNewProduct(@Valid Product product, BindingResult bindingResult) {

		if (bindingResult.hasErrors()) {
			return "form";
		} else {
			System.out.println("-------------" + product.toString());

			productRepo.save(product);
			return "redirect:/" + product.getId();
		}
	}

	// render form for updating record
	@GetMapping("/edit/{id}")
	public String editProduct(@PathVariable(value = "id") Integer id, Model model) {

		Product product = productRepo.findOne(id);

		if (null == product) {
			return null;
		} else {
			model.addAttribute("product", product);
			return "form";
		}
	}

	// deletes a record
	@GetMapping("/delete/{id}")
	public String deleteProduct(@PathVariable(value = "id") Integer id) {

		Product product = productRepo.findOne(id);

		if (null == product) {
			return null;

		} else {
			productRepo.delete(id);
			return "redirect:/";
		}
	}

}

Header -template (header.html)

<div th:fragment="header-css" th:remove="tag">
	<!-- Bootstrap core CSS -->
	<link rel="stylesheet" th:href="@{/css/bootstrap.css}" />

	<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
	<link rel="stylesheet" th:href="@{/css/ie10-viewport-bug-workaround.css}" />

	<!-- Custom styles for this template -->
	<link rel="stylesheet" th:href="@{/css/sticky-footer-navbar.css}" />
	<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" />

	<!-- Just for debugging purposes. Don't actually copy these 2 lines! -->
	<!--[if lt IE 9]><script src="../../assets/js/ie8-responsive-file-warning.js"></script><![endif]-->
	<script th:src="@{/admin/js/ie-emulation-modes-warning.js}"></script>

	<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
	<!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
</div>

<div th:fragment="header-navbar" th:remove="tag">

	<!-- Fixed navbar -->
	<nav class="navbar navbar-default navbar-fixed-top">
		<div class="container">
			<div class="navbar-header">
				<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
					<span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span>
				</button>
				<a class="navbar-brand" href="#">CRUD</a>
			</div>
			<div id="navbar" class="collapse navbar-collapse">
				<ul class="nav navbar-nav">
					<li><a href="/">Home</a></li>
					<li><a href="/all">All Products</a></li>
				</ul>
			</div>
			<!--/.nav-collapse -->
		</div>
	</nav>

</div>

Footer -template (footer.html)

<div th:fragment="footer" th:remove="tag">
	<footer class="footer">
		<div class="container">
			<p class="text-muted">footer</p>
		</div>
	</footer>

	<!-- Bootstrap core JavaScript
    ================================================== -->
	<!-- Placed at the end of the document so the pages load faster -->
	<script th:src="@{/js/jquery.js}"></script>
	<script th:src="@{/js/bootstrap.js}"></script>

	<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
	<script th:src="@{/js/ie10-viewport-bug-workaround.js}"></script>
</div>

First index page (dashbaord.html)

<!DOCTYPE html>
<html lang="en">
<head>
<title>Dashbaord</title>
<div lang="en" th:replace="fragments/header :: header-css" th:remove="tag"></div>
</head>

<body>

	<div lang="en" th:replace="fragments/header :: header-navbar" th:remove="tag"></div>

	<!-- Begin page content -->
	<div class="container">
		<div class="page-header">
			<h1>CRUD App</h1>
		</div>		
	</div>

	<div lang="en" th:replace="fragments/footer :: footer" th:remove="tag"></div>

</body>
</html>

Display all products (index.html)

<!DOCTYPE html>
<html lang="en">
<head>
<title>All Products</title>
<div lang="en" th:replace="fragments/header :: header-css" th:remove="tag"></div>
</head>

<body>

	<div lang="en" th:replace="fragments/header :: header-navbar" th:remove="tag"></div>

	<!-- Begin page content -->
	<div class="container">

		<div class="row">
			<div class="col-md-12">
			
			<h3>Parent Categories</h3>
			<hr />
			
			<div class="alert alert-danger" th:if="${deleteMessage}" th:utext="${deleteMessage + ' Deleted'}"></div>
			
				<table class="table table-bordered">
					<thead>
						<tr>
							<th>#</th>
							<th>Name</th>
							<th>Description</th>
							<th>Action</th>
						</tr>
					</thead>
					<tbody>
						<tr th:each="message : ${allProducts}">
							<td th:text="${message.id}">1</td>
							<td th:text="${message.name}">name</td>
							<td th:text="${message.description}">Description</td>
							<td><a th:href="@{${message.id}}" title="View"> <i class="fa fa-eye fa-lg"></i></a>
								<a th:href="@{'/edit/' + ${message.id}}" title="Update"> <i class="fa fa-pencil fa-lg"></i></a>
								<a th:href="@{'/delete/' + ${message.id}}" title="Update"> <i class="fa fa-trash fa-lg"></i></a>
							</td>
						</tr>
					</tbody>					
				</table>
				
				<a th:href="@{'/add'}" class="col-md-2">
					<button type="button" class="btn btn-primary">Add Category</button>
				</a>

			</div>
		</div>

	</div>

	<div lang="en" th:replace="fragments/footer :: footer" th:remove="tag"></div>

</body>
</html>

HTML form (form.html)

<!DOCTYPE html>
<html lang="en">
<head>
<title>Inside View</title>
<div lang="en" th:replace="fragments/header :: header-css" th:remove="tag"></div>
</head>

<body>

	<div lang="en" th:replace="fragments/header :: header-navbar" th:remove="tag"></div>

	<!-- Begin page content -->
	<div class="container">

		<div class="row">
			<div class="col-md-4">

				<form autocomplete="off" th:action="@{/add}" th:object="${product}" method="post" class="form-horizontal" role="form">

					<h2 class="text-center">Product</h2>

					<input type="hidden" class="form-control" th:field="*{id}" />

					<div class="form-group">
						<label for="name"> Name </label>
						<input type="text" th:field="*{name}" placeholder="Name" class="form-control" />
						<label th:if="${#fields.hasErrors('name')}" th:errors="*{name}" class="alert alert-danger"></label>
					</div>

					<div class="form-group">
						<label for="description"> Description </label>
						<input type="text" th:field="*{description}" placeholder="Description" class="form-control" />
						<label th:if="${#fields.hasErrors('description')}" th:errors="*{description}" class="alert alert-danger"></label>
					</div>
					
					<div class="form-group">
						<label for="description"> Enabled </label>
						<select class="form-control" th:field="*{enabled}">
							<option th:value="${true}">Enabled</option>
							<option th:value="${false}">Disabled</option>
						</select>
					</div>

					<div class="form-group">
						<input type="submit" class="btn btn-primary btn-block" value="Save" />
					</div>

				</form>

			</div>
		</div>

	</div>

	<div lang="en" th:replace="fragments/footer :: footer" th:remove="tag"></div>

</body>
</html>

viewing single product (view.html)

<!DOCTYPE html>
<html lang="en">
<head>
<title>Inside View</title>
<div lang="en" th:replace="fragments/header :: header-css" th:remove="tag"></div>
</head>

<body>

	<div lang="en" th:replace="fragments/header :: header-navbar" th:remove="tag"></div>

	<!-- Begin page content -->
	<div class="container">

		<div class="row">
			<div class="col-md-12">

				<h4 th:text="${product.name + '- view'}"></h4>
				<hr />

				<table class="table table-bordered">
					<thead>
						<tr>
							<th style="width: 20%">Id</th>
							<td th:text="${product.id}">Id</td>
						</tr>
						<tr>
							<th>Name</th>
							<td th:text="${product.name}">Name</td>
						</tr>
						<tr>
							<th>Description</th>
							<td th:text="${product.description}">Description</td>
						</tr>
						<tr>
							<th>Enabled</th>
							<td th:text="${product.enabled}">Enabled</td>
						</tr>

					</thead>

				</table>

			</div>
		</div>

	</div>

	<div lang="en" th:replace="fragments/footer :: footer" th:remove="tag"></div>

</body>
</html>

Properties (application.properties)

spring.thymeleaf.cache=false

spring.datasource.url=jdbc:mysql://localhost:3306/product?useSSL=false
spring.datasource.username= root
spring.datasource.password= 1234
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
spring.jpa.hibernate.ddl-auto = update

Outputs

Project File

spring-thymeleaf-crud.zip