Robert C. Martin coined the term single responsibility principle which states “gather together those things that change for the same reason, and separate those things that change for different reasons.”
A microservices architecture takes this same approach and extends it to the loosely coupled services which can be developed, deployed, and maintained independently. Each of these services is responsible for discrete task and can communicate with other services through simple APIs to solve a larger complex business problem.
When you are just starting to develop microservices, start modestly with just one or two services, learn from them, and with time and experience add more.
In this article, you’ll learn how to read a text file or binary (image) file in Java using various classes and utility methods provided by Java like:
BufferedReader, LineNumberReader, Files.readAllLines, Files.lines, BufferedInputStream, Files.readAllBytes, etc.
Let’s look at each of the different ways of reading a file in Java with the help of examples.
Java read file using BufferedReader
BufferedReader is a simple and performant way of reading text files in Java. It reads text from a character-input stream. It buffers characters to provide efficient reading.
Java read file line by line using Files.readAllLines()
Files.readAllLines() is a utility method of the Java NIO’s Files class that reads all the lines of a file and returns a List<String> containing each line. It internally uses BufferedReader to read the file.
Java read binary file (image file) using BufferedInputStream
All the examples presented in this article so far read textual data from a character-input stream. If you’re reading a binary data such as an image file then you need to use a byte-input stream.
BufferedInputStream lets you read raw stream of bytes. It also buffers the input for improving performance
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
public class BufferedInputStreamImageCopyExample {
public static void main(String[] args) {
try(InputStream inputStream = Files.newInputStream(Paths.get("sample.jpg"));
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
OutputStream outputStream = Files.newOutputStream(Paths.get("sample-copy.jpg"));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream)) {
byte[] buffer = new byte[4096];
int numBytes;
while ((numBytes = bufferedInputStream.read(buffer)) != -1) {
bufferedOutputStream.write(buffer, 0, numBytes);
}
} catch (IOException ex) {
System.out.format("I/O error: %s%n", ex);
}
}
}
Java read file into []byte using Files.readAllBytes()
If you want to read the entire contents of a file in a byte array then you can use the Files.readAllBytes() method.
import com.sun.org.apache.xpath.internal.operations.String;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
public class FilesReadAllBytesExample {
public static void main(String[] args) {
try {
byte[] data = Files.readAllBytes(Paths.get("demo.txt"));
// Use byte data
} catch (IOException ex) {
System.out.format("I/O error: %s%n", ex);
}
}
}
Scaffolding, in modern web apps, has been made popular by tools like yeoman. These tools generate a standard prototype for your project with everything you need to get up and running, including the directory structure, the build system, the project dependencies etc.
Spring Boot has its own scaffolding tool. There are two ways to quickly bootstrap your Spring boot application –
Spring Initializer is a web tool which helps in quickly bootstrapping spring boot projects.
The tool is very simple to use. You just need to enter your project details and required dependencies. The tool will generate and download a zip file containing a standard spring boot project based on the details you have entered.
Follow these steps to generate a new project using Spring Initializer web app –
Step 2. Select Maven Project or Gradle Project in the Project section. Maven Project is the default.
Step 3. Select the language of your choice Java, Kotlin, or Groovy. Java is the default
Step 4. Select the Spring Boot version. The current stable release is the default.
Step 5. Enter group and artifact details in Project metadata.
Step 5.1. Click Options to customize more specific details about your project like Name, Description, Package name, etc.
Step 6. Search for dependencies in the Dependencies section and press enter to add them to your project. You can also click on the menu icon besides the search icon to see the list of all the dependencies available and select the ones that you want to include in your project.
Step 7. Once you have selected all the dependencies and entered the project details, click Generate to generate your project.
The tool will generate and download a zip file containing your project’s prototype. The generated prototype will have all the project directories, the main application class and the pom.xml or build.gradle file with all the dependencies you had selected.
Just unzip the file and import it in an IDE of your choice and start working.
I created a maven project using Spring Initializer while writing this post and here is what I got –
You see! I’ve got everything that is needed to get started. A standard spring boot project structure containing all the required directories along with a pom.xml file containing all the dependencies.
You can go to the project’s root directory and type the following command in your terminal to run the application.
$ mvn spring-boot:run
If you have generated a gradle project, you can run it using –
$ gradle bootRun
Spring Boot CLI
Spring Boot CLI is a command line tool that helps in quickly prototyping with Spring. You can also run Groovy scripts using this tool.
Checkout the Official Spring Boot documentation for instructions on how to install Spring Boot CLI for your operating system. Once installed, type the following command to see what you can do with Spring Boot CLI –
$ spring --help
usage: spring [--help] [--version]
<command> [<args>]
Available commands are:
run [options] <files> [--] [args]
Run a spring groovy script
grab
Download a spring groovy scripts dependencies to ./repository
jar [options] <jar-name> <files>
Create a self-contained executable jar file from a Spring Groovy script
war [options] <war-name> <files>
Create a self-contained executable war file from a Spring Groovy script
install [options] <coordinates>
Install dependencies to the lib/ext directory
uninstall [options] <coordinates>
Uninstall dependencies from the lib/ext directory
init [options] [location]
Initialize a new project using Spring Initializr (start.spring.io)
encodepassword [options] <password to encode>
Encode a password for use with Spring Security
shell
Start a nested shell
Common options:
--debug Verbose mode
Print additional status information for the command you are running
See 'spring help <command>' for more information on a specific command.
In this post, we’ll look at spring init command to generate a new spring boot project. The spring init command internally uses Spring Initializr web app to generate and download the project.
Open your terminal and type –
$ spring help init
to get all the information about init command.
Following are few examples of how to use the command –
To list all the capabilities of the service:
$ spring init --list
To creates a default project:
$ spring init
To create a web my-app.zip:
$ spring init -d=web my-app.zip
To create a web/data-jpa gradle project unpacked:
$ spring init -d=web,jpa --build=gradle my-dir
Let’s generate a maven web application with JPA, Mysql and Thymleaf dependencies –
$ spring init -n=blog-sample -d=web,data-jpa,mysql,devtools,thymeleaf -g=com.test-a=blog-sample
Using service at https://start.spring.io
Content saved to 'blog-sample.zip'
This command will create a new spring boot project with all the specified settings.
Quick and Simple! isn’t it?
Conclusion
Congratulations folks! We explored two ways of quickly bootstrapping spring boot applications. I always use one of these two methods whenever I have to work with a new Spring Boot app. It saves a lot of time for me.
In this article, you’ll learn how to map a one-to-many database relationship at the object level using JPA and Hibernate.
Consider the following two tables – posts and comments of a Blog database schema where the posts table has a one-to-many relationship with the comments table –
We’ll create a project from scratch and learn how to go about implementing such one-to-many relationship at the object level using JPA and hibernate.
We’ll also write REST APIs to perform CRUD operations on the entities so that you fully understand how to actually use these relationships in the real world.
Creating the Project
If you have Spring Boot CLI installed, then you can type the following command in your terminal to generate the project –
spring init -n=jpa-one-to-many-demo -d=web,jpa,mysql --package-name=com.example.jpa jpa-one-to-many-demo
Alternatively, You can generate the project from Spring Initializr web tool by following the instructions below –
Click Options dropdown to see all the options related to project metadata.
Change Package Name to “com.example.jpa”
Select Web, JPA and Mysql dependencies.
Click Generate to download the project.
Following is the directory structure of the project for your reference –
“Your bootstrapped project won’t have model, controller, repository and exception packages, and all the classes inside these packages at this point. We’ll create them shortly.“
Configuring the Database and Logging
Since we’re using MySQL as our database, we need to configure the database URL, username, and password so that Spring can establish a connection with the database on startup. Open src/main/resources/application.properties file and add the following properties to it –
# DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url=jdbc:mysql://localhost:3306/jpa_one_to_many_demo?useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false
spring.datasource.username=root
spring.datasource.password=root
# Hibernate
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = update
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type=TRACE
Don’t forget to change the spring.datasource.username and spring.datasource.password as per your MySQL installation. Also, create a database named jpa_one_to_many_demo in MySQL before proceeding to the next section.
You don’t need to create any tables. The tables will automatically be created by hibernate from the Post and Comment entities that we will define shortly. This is made possible by the property spring.jpa.hibernate.ddl-auto = update.
We have also specified the log levels for hibernate so that we can debug all the SQL statements and learn what hibernate does under the hood.
The best way to model a one-to-many relationship in hibernate
I have been working with hibernate for quite some time and I’ve realized that the best way to model a one-to-many relationship is to use just @ManyToOne annotation on the child entity.
The second best way is to define a bidirectional association with a @OneToMany annotation on the parent side of the relationship and a @ManyToOne annotation on the child side of the relationship. The bidirectional mapping has its pros and cons. I’ll demonstrate these pros and cons in the second section of this article. I’ll also tell you when a bidirectional mapping is a good fit.
But let’s first model our one-to-many relationship in the best way possible.
Defining the Domain Models
In this section, we’ll define the domain models of our application – Post and Comment.
Note that both Post and Comment entities contain some common auditing related fields like created_at and updated_at.
We’ll abstract out these common fields in a separate class called AuditModel and extend this class in the Post and Comment entities.
We’ll also use Spring Boot’s JPA Auditing feature to automatically populate the created_at and updated_at fields while persisting the entities.
1. AuditModel
package com.example.jpa.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Date;
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@JsonIgnoreProperties(
value = {"createdAt", "updatedAt"},
allowGetters = true
)
public abstract class AuditModel implements Serializable {
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_at", nullable = false, updatable = false)
@CreatedDate
private Date createdAt;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "updated_at", nullable = false)
@LastModifiedDate
private Date updatedAt;
public Date getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
public Date getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(Date updatedAt) {
this.updatedAt = updatedAt;
}
}
In the above class, we’re using Spring Boot’s AuditingEntityListener to automatically populate the createdAt and updatedAt fields.
Enabling JPA Auditing
To enable JPA Auditing, you’ll need to add @EnableJpaAuditing annotation to one of your configuration classes. Open the main class JpaOneToManyDemoApplication.java and add the @EnableJpaAuditing to the main class like so –
package com.example.jpa;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@SpringBootApplication
@EnableJpaAuditing
public class JpaOneToManyDemoApplication {
public static void main(String[] args) {
SpringApplication.run(JpaOneToManyDemoApplication.class, args);
}
}
2. Post model
package com.example.jpa.model;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Entity
@Table(name = "posts")
public class Post extends AuditModel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
@Size(max = 100)
@Column(unique = true)
private String title;
@NotNull
@Size(max = 250)
private String description;
@NotNull
@Lob
private String content;
// Getters and Setters (Omitted for brevity)
}
The Comment model contains the @ManyToOne annotation to declare that it has a many-to-one relationship with the Post entity. It also uses the @JoinColumn annotation to declare the foreign key column.
Defining the Repositories
Next, We’ll define the repositories for accessing the data from the database. Create a new package called repository inside com.example.jpa package and add the following interfaces inside the repository package –
Writing the REST APIs to perform CRUD operations on the entities
Let’s now write the REST APIs to perform CRUD operations on Post and Comment entities.
All the following controller classes are define inside com.example.jpa.controller package.
1. PostController (APIs to create, retrieve, update, and delete Posts)
package com.example.jpa.controller;
import com.example.jpa.exception.ResourceNotFoundException;
import com.example.jpa.model.Post;
import com.example.jpa.repository.PostRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@RestController
public class PostController {
@Autowired
private PostRepository postRepository;
@GetMapping("/posts")
public Page<Post> getAllPosts(Pageable pageable) {
return postRepository.findAll(pageable);
}
@PostMapping("/posts")
public Post createPost(@Valid @RequestBody Post post) {
return postRepository.save(post);
}
@PutMapping("/posts/{postId}")
public Post updatePost(@PathVariable Long postId, @Valid @RequestBody Post postRequest) {
return postRepository.findById(postId).map(post -> {
post.setTitle(postRequest.getTitle());
post.setDescription(postRequest.getDescription());
post.setContent(postRequest.getContent());
return postRepository.save(post);
}).orElseThrow(() -> new ResourceNotFoundException("PostId " + postId + " not found"));
}
@DeleteMapping("/posts/{postId}")
public ResponseEntity<?> deletePost(@PathVariable Long postId) {
return postRepository.findById(postId).map(post -> {
postRepository.delete(post);
return ResponseEntity.ok().build();
}).orElseThrow(() -> new ResourceNotFoundException("PostId " + postId + " not found"));
}
}
2. CommentController (APIs to create, retrieve, update, and delete Comments)
package com.example.jpa.controller;
import com.example.jpa.exception.ResourceNotFoundException;
import com.example.jpa.model.Comment;
import com.example.jpa.repository.CommentRepository;
import com.example.jpa.repository.PostRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@RestController
public class CommentController {
@Autowired
private CommentRepository commentRepository;
@Autowired
private PostRepository postRepository;
@GetMapping("/posts/{postId}/comments")
public Page<Comment> getAllCommentsByPostId(@PathVariable (value = "postId") Long postId,
Pageable pageable) {
return commentRepository.findByPostId(postId, pageable);
}
@PostMapping("/posts/{postId}/comments")
public Comment createComment(@PathVariable (value = "postId") Long postId,
@Valid @RequestBody Comment comment) {
return postRepository.findById(postId).map(post -> {
comment.setPost(post);
return commentRepository.save(comment);
}).orElseThrow(() -> new ResourceNotFoundException("PostId " + postId + " not found"));
}
@PutMapping("/posts/{postId}/comments/{commentId}")
public Comment updateComment(@PathVariable (value = "postId") Long postId,
@PathVariable (value = "commentId") Long commentId,
@Valid @RequestBody Comment commentRequest) {
if(!postRepository.existsById(postId)) {
throw new ResourceNotFoundException("PostId " + postId + " not found");
}
return commentRepository.findById(commentId).map(comment -> {
comment.setText(commentRequest.getText());
return commentRepository.save(comment);
}).orElseThrow(() -> new ResourceNotFoundException("CommentId " + commentId + "not found"));
}
@DeleteMapping("/posts/{postId}/comments/{commentId}")
public ResponseEntity<?> deleteComment(@PathVariable (value = "postId") Long postId,
@PathVariable (value = "commentId") Long commentId) {
return commentRepository.findByIdAndPostId(commentId, postId).map(comment -> {
commentRepository.delete(comment);
return ResponseEntity.ok().build();
}).orElseThrow(() -> new ResourceNotFoundException("Comment not found with id " + commentId + " and postId " + postId));
}
}
The ResourceNotFoundException Class
Both the Post and Comment Rest APIs throw ResourceNotFoundException when a post or comment could not be found. Following is the definition of the ResourceNotFoundException.
package com.example.jpa.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException() {
super();
}
public ResourceNotFoundException(String message) {
super(message);
}
public ResourceNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
I have added the @ResponseStatus(HttpStatus.NOT_FOUND) annotation to the above exception class to tell Spring Boot to respond with a 404 status when this exception is thrown.
Running the Application and Testing the APIs via a Postman
You can run the application by typing the following command in the terminal –
mvn spring-boot:run
Let’s now test the APIs via Postman.
Create Post POST /posts
Get paginated Posts GET /posts?page=0&size=2&sort=createdAt,desc
Create Comment POST /posts/{postId}/comments
Get paginated comments GET /posts/{postId}/comments?page=0&size=3&sort=createdAt,desc
You can test other APIs in the same way.
How to define a bidirectional one-to-many mapping and when should you use it
The Internet is flooded with examples of bidirectional one-to-many mapping. But it’s not the best and the most efficient way to model a one-to-many relationship.
Here is the bidirectional version of the one-to-many relationship between the Post and Comment entities –
package com.example.jpa.model;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
@Entity
@Table(name = "comments")
public class Comment extends AuditModel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
@Lob
private String text;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id", nullable = false)
private Post post;
// Getters and Setters (Omitted for brevity)
}
The idea with bidirectional one-to-many association is to allow you to keep a collection of child entities in the parent, and enable you to persist and retrieve the child entities via the parent entity. This is made possible via Hibernate’s entity state transitions and dirty checking mechanism.
For example, here is how you could persist comments via post entity in the bidirectional mapping –
// Create a Post
Post post = new Post("post title", "post description", "post content");
// Create Comments
Comment comment1 = new Comment("Great Post!");
comment1.setPost(post);
Comment comment2 = new Comment("Really helpful Post. Thanks a lot!");
comment2.setPost(post);
// Add comments in the Post
post.getComments().add(comment1);
post.getComments().add(comment2);
// Save Post and Comments via the Post entity
postRepository.save(post);
Hibernate automatically issues insert statements and saves the comments added to the post.
Similarly, you could fetch comments via the post entity like so –
// Retrieve Post
Post post = postRepository.findById(postId)
// Get all the comments
Set<Comment> comments = post.getComments()
When you write post.getComments(), hibernate loads all the comments from the database if they are not already loaded.
Problems with bidirectional one-to-many mapping
A bidirectional mapping tightly couples the many-side of the relationship to the one-side.
In our example, If you load comments via the post entity, you won’t be able to limit the number of comments loaded. That essentially means that you won’t be able to paginate.
If you load comments via the post entity, you won’t be able to sort them based on different properties. You can define a default sorting order using @OrderColumn annotation but that will have performance implications.
When can I use a bidirectional one-to-many mapping
A bidirectional one-to-many mapping might be a good idea if the number of child entities is limited.
Moreover, A bidirectional mapping tightly couples the many-side of the relationship to the one-side. Many times, this tight coupling is desired.
For example, Consider a Survey application with a Question and an Option entity exhibiting a one-to-many relationship between each other.
In the survey app, A Question can have a set of Options. Also, every Question is tightly coupled with its Options. When you create a Question, you’ll also provide a set of Options. And, when you retrieve a Question, you will also need to fetch the Options.
Moreover, A Question can have at max 4 or 5 Options. These kind of cases are perfect for bi-directional mappings.
So, To decide between bidirectional and unidirectional mappings, you should think whether the entities have a tight coupling or not.
Conclusion
That’s all folks! In this article, You learned how to map a one-to-many database relationship using JPA and Hibernate.
More Resources
You may wanna check out the following articles by Vlad Mihalcea to learn more about Hibernate and it’s performance –
Thanks for reading. I hope you found the content useful. Consider subscribing to my newsletter if you don’t wanna miss future articles from The Fusebes Blog.
In this article, you’ll learn how to get the current epoch timestamp in milliseconds precision in Java.
Get current timestamp in Java using System.currentTimeMillis()
public class CurrentEpochTimestampExample {
public static void main(String[] args) {
// Get epoch timestamp using System.currentTimeMillis()
long currentTimestamp = System.currentTimeMillis();
System.out.println("Current epoch timestamp in millis: " + currentTimestamp);
}
}
Get current timestamp in Java using the Instant class
import java.time.Instant;
public class CurrentEpochTimestampExample {
public static void main(String[] args) {
// Get current timestamp using Instant
currentTimestamp = Instant.now().toEpochMilli();
System.out.println("Current epoch timestamp in millis: " + currentTimestamp);
}
}
In this article, You’ll learn how to map a collection of basic as well as embeddable types using JPA’s @ElementCollection and @CollectionTable annotations.
Let’s say that the users of your application can have multiple phone numbers and addresses. To map this requirement into the database schema, you need to create separate tables for storing the phone numbers and addresses –
Both the tables user_phone_numbers and user_addresses contain a foreign key to the users table.
You can implement such relationship at the object level using JPA’s one-to-many mapping. But for basic and embeddable types like the one in the above schema, JPA has a simple solution in the form of ElementCollection.
Let’s create a project from scratch and learn how to use an ElementCollection to map the above schema in your application using hibernate.
Creating the Application
If you have Spring Boot CLI installed, then simply type the following command in your terminal to generate the application –
spring init -n=jpa-element-collection-demo -d=web,jpa,mysql --package-name=com.example.jpa jpa-element-collection-demo
Alternatively, You can use Spring Initializr web app to generate the application by following the instructions below –
Click Options dropdown to see all the options related to project metadata.
Change Package Name to “com.example.jpa”
Select Web, JPA and Mysql dependencies.
Click Generate to generate and download the project.
Configuring MySQL database and Hibernate Log levels
Let’s first configure the database URL, username, password, hibernate log levels and other properties.
Open src/main/resources/application.properties and add the following properties to it –
# DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url=jdbc:mysql://localhost:3306/jpa_element_collection_demo?useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false
spring.datasource.username=root
spring.datasource.password=root
# Hibernate
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = update
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type=TRACE
Please change spring.datasource.username and spring.datasource.password as per your MySQL installation. Also, create a database named jpa_element_collection_demo before proceeding to the next section.
Defining the Entity classes
Next, We’ll define the Entity classes that will be mapped to the database tables we saw earlier.
Before defining the User entity, let’s first define the Address type which will be embedded inside the User entity.
All the domain models will go inside the package com.example.jpa.model.
We use @ElementCollection annotation to declare an element-collection mapping. All the records of the collection are stored in a separate table. The configuration for this table is specified using the @CollectionTable annotation.
The @CollectionTable annotation is used to specify the name of the table that stores all the records of the collection, and the JoinColumn that refers to the primary table.
Moreover, When you’re using an Embeddable type with Element Collection, you can use the @AttributeOverrides and @AttributeOverride annotations to override/customize the fields of the embeddable type.
Defining the Repository
Next, Let’s create the repository for accessing the user’s data from the database. You need to create a new package called repository inside com.example.jpa package and add the following interface inside the repository package –
Finally, Let’s write the code to test our setup in the main class JpaElementCollectionDemoApplication.java –
package com.example.jpa;
import com.example.jpa.model.Address;
import com.example.jpa.model.User;
import com.example.jpa.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.util.HashSet;
import java.util.Set;
@SpringBootApplication
public class JpaElementCollectionDemoApplication implements CommandLineRunner {
@Autowired
private UserRepository userRepository;
public static void main(String[] args) {
SpringApplication.run(JpaElementCollectionDemoApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
// Cleanup database tables.
userRepository.deleteAll();
// Insert a user with multiple phone numbers and addresses.
Set<String> phoneNumbers = new HashSet<>();
phoneNumbers.add("+91-9999999999");
phoneNumbers.add("+91-9898989898");
Set<Address> addresses = new HashSet<>();
addresses.add(new Address("747", "Golf View Road", "Bangalore",
"Karnataka", "India", "560008"));
addresses.add(new Address("Plot No 44", "Electronic City", "Bangalore",
"Karnataka", "India", "560001"));
User user = new User("Yaniv Levy", "yaniv@fusebes.com",
phoneNumbers, addresses);
userRepository.save(user);
}
}
The main class implements the CommandLineRunner interface and provides the implementation of its run() method.
The run() method is executed post application startup. In the run() method, we first cleanup all the tables and then insert a new user with multiple phone numbers and addresses.
You can run the application by typing mvn spring-boot:run from the root directory of the project.
After running the application, Go ahead and check all the tables in MySQL. The users table will have a new entry, the user_phone_numbers and user_addresses table will have two new entries –
mysql> select * from users;
+----+-----------------------+--------------------+
| id | email | name |
+----+-----------------------+--------------------+
| 3 | yaniv@fusebes.com | Yaniv Levy |
+----+-----------------------+--------------------+
1 row in set (0.01 sec)
mysql> select * from user_phone_numbers;
+---------+----------------+
| user_id | phone_number |
+---------+----------------+
| 3 | +91-9898989898 |
| 3 | +91-9999999999 |
+---------+----------------+
2 rows in set (0.00 sec)
mysql> select * from user_addresses;
+---------+--------------+-----------------+-----------+---------+-----------+----------+
| user_id | house_number | street | city | country | state | zip_code |
+---------+--------------+-----------------+-----------+---------+-----------+----------+
| 3 | 747 | Golf View Road | Bangalore | India | Karnataka | 560008 |
| 3 | Plot No 44 | Electronic City | Bangalore | India | Karnataka | 560001 |
+---------+--------------+-----------------+-----------+---------+-----------+----------+
2 rows in set (0.00 sec)
Conclusion
That’s all in this article Folks. I hope you learned how to use hibernate’s element-collection with Spring Boot.
The two best advantages of boot is simplified & version conflict free dependency management through the starter POMs and opinionated auto-configuration of most commonly used libraries and behaviors.
The embedded jars enables to package the web applications as jar file which can be run anywhere.
It’s actuator module provides HTTP endpoints to access application internals like detailed metrics, application inner working, health status, etc.
On disadvantages side, they are very few. Still many developers may see the transitive dependencies included with starter poms as burden to deployment packaging.
Also, it’s auto-configuration feature may enable many such features which we may never use in application lifecycle and they will sit there all the time initialized and fully configured. It may cause some un-necessary resource utilization.
Spring boot’s actuator module allows us to monitor and manage application usages in production environment, without coding and configuration for any of them. These monitoring and management information is exposed via REST like endpoint URLs.
The simplest way to enable the features is to add a dependency to the spring-boot-starter-actuator starter pom file.
Following is an example program to add pages to a PDF document using Java.
import java.io.File;
import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
public class AddingPagesToPdf {
public static void main(String args[]) throws IOException {
//Creating PDF document object
PDDocument document = new PDDocument();
File file = new File("C:/pdfBox/AddPages.pdf");
PDDocument.load(file);
for (int i=0; i<10; i++){
//Creating a blank page
PDPage blankPage = new PDPage();
//Adding the blank page to the document
document.addPage(blankPage);
}
//Saving the document
document.save("C:/pdfBox/AddPages_OP.pdf");
System.out.println("PDF created");
//Closing the document
document.close();
}
}
So in Part 1 of this article, we’ve learned what micro-frontends are, what they are meant for, and what the main concepts associated with them are. In this part, we are going to dive deep into various micro-frontend architecture types. We will understand what characteristics they share and what they don’t. We’ll see how complex each architecture is to implement. Then through the documents-to-applications continuum concept, we will identify our application type and choose the proper micro-frontend architecture that meets our application’s needs at most.
Micro-frontend architecture characteristics
In order to define micro-frontend architecture types, let’s reiterate over two micro-frontend integration concepts that we learned in the first part: (Fig. 1 and 2).
routing/page transitions
For page transition, we’ve seen that there are server and client-side routings, which in another way are called hard and soft transitions. It’s “hard” because with server-side routing, each page transition leads to a full page reload, which means for a moment the user sees a blank page until the content starts to render and show up. And client-side routing is called soft because on page transition, only some part of the page content is being reloaded (usually header, footer, and some common content stays as it is) by JavaScript. So there is no blank page, and, also the new content is smaller so it arrives earlier than during the full page reload. Therefore, with soft transitions, we undoubtedly have a better user experience than with hard ones.
Figure 1. routing/page transition types
Currently, almost all modern web applications are Single Page Applications (SPAs). This is a type of application where all page transitions are soft and smooth. When speaking about micro-frontends, we can consider each micro-frontend as a separate SPA, so the page transitions within each of them can also be considered soft. From a micro-frontend architectural perspective, by saying page transitions, we meant the transitions from one micro frontend to another and not the local transitions within a micro frontend. Therefore this transition type is one of the key characteristics of a micro-frontend architecture type.
composition/rendering
For composition, we’ve spoken about the server and client-side renderings. In the 2000s, when JavaScript was very poor and limited, almost all websites were fully rendered on the server-side. With the evolution of web applications and JavaScript, client-side rendering became more and more popular. SPAs mentioned earlier also are applications with client-side rendering.
Figure 2. composition/rendering types
Currently, when saying server-side rendering or universal rendering, we don’t assume that the whole application is rendered on the server-side — it’s more like hybrid rendering where some content is rendered on the server-side and the other part on the client-side. In the first article about micro-frontends, we’ve spoken about the cases when universal rendering can be useful. So the rendering is the second characteristic of a micro-frontend architecture type.
Architecture types
Now that we have defined two characteristics of micro-frontend architecture, we can extract four types based on various combinations of those two.
Linked Applications
Hard transitions;
Client-side rendering;
This is the simplest case where the content rendering is being done on the client-side and where the transition between micro-frontends is hard. As an example, we can consider two SPAs that are hosted under the same domain (http://team.com). A simple Nginx proxy server serves the first micro-frontend on the path “/a” and the second micro-frontend on the path “/b.” Page transitions within each micro-frontend are smooth as we assumed they are SPAs.
The transition from one micro-frontend to another happens via links which in fact is a hard transition and leads to a full page reload (Fig. 3). This micro-frontend architecture has the simplest implementation complexity.
Figure 3. Linked application transition model
Linked Universal Applications
Hard transitions;
Universal rendering;
If we add universal rendering features to the previous case, we’ll get the Linked Universal Applications architecture type. This means that some content of either one or many micro frontends renders on the server-side. But still, the transition from one micro to another is done via links. This architecture type is more complex than the first one as we have to implement server-side rendering.
Unified Applications
Soft transitions;
Client-side rendering;
Here comes my favorite part. To have a soft transition between micro-frontends, there should be a shell-application that will act as an umbrella for all micro-frontend applications. It will only have a markup layout and, most importantly, a top-level client routing. So the transition from path “/a” to path “/b” will be handed over to the shell-application (Fig. 4). As it is a client-side routing and is the responsibility of JavaScript, it can be done softly, making a perfect user experience. Though this type of application is fully rendered on the client-side, there is no universal rendering of this architecture type.
Figure 4. Unified application with a shell application
Unified Universal Applications
Soft transitions;
Universal rendering;
And here we reach the most powerful and, at the same time, the most complex architecture type where we have both soft transitions between micro-frontends and universal rendering (Fig. 5).
Figure 5. Micro-frontend architecture types
A logical question can arise like, “oh, why don’t we pick the fourth and the best option?” Simply because these four architecture types have different levels of complexity, and the more complex our architecture is, the more we will pay for it for both implementation and maintenance (Fig. 6). So there is no absolute right or wrong architecture. We just have to find a way to understand how to choose the right architecture for our application, and we are going to do it in the next section.
Figure 6. Micro-frontend architecture types based on their implementation complexities.
Which architecture to choose?
There is an interesting concept called the documents-to-applications continuum (Fig. 7). It’s an axis with two ends where we have more content-centric websites, and on the other side, we have pure web applications that are behavior-centric.
Content-centric: the priority is given to the content. Think of some news or a blog website where users usually open an article. The content is what matters in such websites; the faster it loads, the more time the users save. There are not so many transitions in these websites, so they can easily be hard, and no one will care.
Behavior-centric: the priority is given to the user experience. These are pure applications like online code editors, and playgrounds, etc. In these applications, you are usually in a flow of multiple actions. Thus there are many route/state transitions and user interactions along with the flow. For such types of applications having soft transitions is key for good UX.
Figure 7. Correlation of the document-to-applications continuum and micro-frontend architecture types.
From Fig. 7, we can see that there is an overlap of content and behavior-centric triangles. This is the area where the progressive web applications lay. These are applications where both the content and the user experience are important. A good example is an e-commerce web application.
The faster the first page loads, the more pleasant it will be for the customers to stay on the website and shop.
The first example is that of a content-centric application, while the second one is behavior-centric.
Now let’s see where our architecture types lay in the document-to-applications continuum. Since we initially assumed that each micro-frontend is a SPA by itself, we can assume that all our architecture types lay in the behavior-centric spectrum. Note that this assumption of SPA’s is not a prerogative and is more of an opinionated approach. As both linked universal apps and universal unified apps have universal rendering features, they will lay in progressive web apps combined with both content and behavior-centric spectrums. Linked apps and unified apps are architecture types that are more suited to web applications where the best user experience has higher priority than the content load time.
First of all, we have to identify our application requirements and where it is positioned in the document-to-application continuum. Then it’s all about what complexity level of implementation we can afford.
Conclusion
This time, as we already learned the main concepts of micro-frontends, we dived deep into micro-frontend applications’ high-level architecture. We defined architecture characteristics and came up with four types. And at the end, we learned how and based on what to choose our architecture type. I’d like to mention that I’ve gained most of the knowledge on micro-frontends and especially the things I wrote about in this article from the book by Micheal Geers “ Micro frontends in Action,” which I recommend reading if you want to know much more about this topic.
What’s next?
For the next chapter, we will be going towards the third architecture — Unified apps, as this type of architecture, mostly fits the AI annotation platform. We will be learning how to build unified micro-frontends. So stay tuned for the last third part.