In this tutorial, we’ll learn about Composite Primary Keys and the corresponding annotations in JPA.
2. Composite Primary Keys
A composite primary key – also called a composite key – is a combination of two or more columns to form a primary key for a table.
In JPA, we have two options to define the composite keys: The @IdClass and @EmbeddedId annotations.
In order to define the composite primary keys, we should follow some rules:
The composite primary key class must be public
It must have a no-arg constructor
It must define equals() and hashCode() methods
It must be Serializable
3. The IdClass Annotation
Let’s say we have a table called Account and it has two columns – accountNumber, accountType – that form the composite key. Now we have to map it in JPA.
As per the JPA specification, let’s create an AccountId class with these primary key fields:
public class AccountId implements Serializable {
private String accountNumber;
private String accountType;
// default constructor
public AccountId(String accountNumber, String accountType) {
this.accountNumber = accountNumber;
this.accountType = accountType;
}
// equals() and hashCode()
}
Next, let’s associate the AccountId class with the entity Account.
In order to do that, we need to annotate the entity with the @IdClass annotation. We must also declare the fields from the AccountId class in the entity Account and annotate them with @Id:
@Entity
@IdClass(AccountId.class)
public class Account {
@Id
private String accountNumber;
@Id
private String accountType;
// other fields, getters and setters
}
4. The EmbeddedId Annotation
@EmbeddedId is an alternative to the @IdClass annotation.
Let’s consider another example where we have to persist some information of a Book with title and language as the primary key fields.
In this case, the primary key class, BookId, must be annotated with @Embeddable:
@Embeddable
public class BookId implements Serializable {
private String title;
private String language;
// default constructor
public BookId(String title, String language) {
this.title = title;
this.language = language;
}
// getters, equals() and hashCode() methods
}
Then, we need to embed this class in the Book entity using @EmbeddedId:
@Entity
public class Book {
@EmbeddedId
private BookId bookId;
// constructors, other fields, getters and setters
}
5. @IdClass vs @EmbeddedId
As we just saw, the difference on the surface between these two is that with @IdClass, we had to specify the columns twice – once in AccountId and again in Account. But, with @EmbeddedId we didn’t.
There are some other tradeoffs, though.
For example, these different structures affect the JPQL queries that we write.
For example, with @IdClass, the query is a bit simpler:
SELECT account.accountNumber FROM Account account
With @EmbeddedId, we have to do one extra traversal:
SELECT book.bookId.title FROM Book book
Also, @IdClass can be quite useful in places where weare using a composite key class that we can’t modify.
Finally, if we’re going to access parts of the composite key individually, we can make use of @IdClass, but in places where we frequently use the complete identifier as an object, @EmbeddedId is preferred.
6. Conclusion
In this quick article, we explore composite primary keys in JPA.
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.
To create any simple spring boot application, we need to start by creating a pom.xml file. Add spring-boot-starter-parent as parent of the project which makes it a spring boot application.pom.xml<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.example</groupId><artifactId>myproject</artifactId><version>0.0.1-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.1.RELEASE</version></parent></project>Above is simplest spring boot application which can be packaged as jar file. Now import the project in your IDE (optional).
Now we can start adding other starter dependencies that we are likely to need when developing a specific type of application. For example, for a web application, we add a spring-boot-starter-web dependency.pom.xml<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies>
Start adding application business logic, views and domain data at this step. By default, Maven compiles sources from src/main/java so create your application code inside this folder only.As the very initial package, add the application bootstrap class and add @SpringBootApplication annotation. This class will be used to run the application. It’s main() method acts as the application entry point.MyApplication.javaimportorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublicclassMyApplication {publicstaticvoidmain(String[] args) {SpringApplication.run(MyApplication.class, args);}}
The SpringApplication class provided in above main() method bootstraps our application, starting Spring, which, in turn, starts the auto-configured Tomcat web server. MyApplication (method argument) indicates the primary Spring component.
Since we used the spring-boot-starter-parent POM, we have a useful “run” goal that we can use to start the application. Type 'mvn spring-boot:run' from the root project directory to start the application.It will start the application which we can verify in console logs as well as in browser by hitting URL: localhost:8080.
@EnableAutoConfiguration – It enable auto-configuration mechanism.
@ComponentScan – enable component scanning in application classpath.
@SpringBootApplication – enable all 3 above three things in one step i.e. enable auto-configuration mechanism, enable component scanning and register extra beans in the context.
@ImportAutoConfiguration
@AutoConfigureBefore, @AutoConfigureAfter, @AutoConfigureOrder – shall be used if the configuration needs to be applied in a specific order (before of after).
@Conditional – annotations such as @ConditionalOnBean, @ConditionalOnWebApplication or @ConditionalOnClass allow to register a bean only when the condition meets.
Spring Boot starters are maven templates that contain a collection of all the relevant transitive dependencies that are needed to start a particular functionality.
For example, If we want to create a Spring WebMVC application then in a traditional setup, we would have included all required dependencies ourselves. It leaves the chances of version conflict which ultimately result in more runtime exceptions.
With Spring boot, to create web MVC application, all we need to import is spring-boot-starter-web dependency. Transitively, it brings in all other required dependencies to build a web application e.g. spring-webmvc, spring-web, hibernate-validator, tomcat-embed-core, tomcat-embed-el, tomcat-embed-websocket, jackson-databind, jackson-datatype-jdk8, jackson-datatype-jsr310 and jackson-module-parameter-names.
Spring boot auto configuration scans the classpath, finds the libraries in the classpath and then attempt to guess the best configuration for them, and finally configure all such beans.
Auto-configuration tries to be as intelligent as possible and will back-away as we define more of our own custom configuration. It is always applied after user-defined beans have been registered.
Auto-configuration works with help of @Conditional annotations such as @ConditionalOnBean and @ConditionalOnClass.
For example, look at AopAutoConfiguration class.
If class path scanning finds EnableAspectJAutoProxy, Aspect, Advice and AnnotatedElement classes and spring.aop.auto=false is not present in properties file then Spring boot will configure the Spring AOP module for us.
Spring boot is very easy to use and it does a lot of things under the hood, you might not be aware of. In future, a good developer will be who will know exactly what is going on behind spring boot auto configuration, how to use it in your favor and how to disable certain sections which you do not want into your project.
To understand most basic things behind spring boot, we will create a minimum boot application with single dependency and single launch class file. We will then analyze the startup logs to get the insights.
Create Spring boot application with launch class
Create a new maven project in eclipse with archetype “maven-archetype-quickstart“.
Update pom.xml file with spring-boot-starter-web dependency and plugin information.<projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/maven-4.0.0.xsd;<modelVersion>4.0.0</modelVersion><groupId>com.fusebes</groupId><artifactId>springbootdemo</artifactId><version>0.0.1-SNAPSHOT</version><packaging>jar</packaging><name>springbootdemo</name><url>http://maven.apache.org</url><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.0.RELEASE</version></parent><properties><java.version>1.8</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build><repositories><repository><id>repository.spring.release</id><name>Spring GA Repository</name><url>http://repo.spring.io/release</url></repository></repositories></project>
Create launch application.packagecom.fusebes.demo;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.context.ApplicationContext;@SpringBootApplicationpublicclassApp {publicstaticvoidmain(String[] args) {ApplicationContext ctx = SpringApplication.run(App.class, args);}}What this launch class does?Above class is called spring boot application launch class. It used to Bootstrap and launch a Spring application from a Java main() method. It typically does following things –
Create an instance of Spring’s ApplicationContext.
Enable the functionality to accept command-line arguments and expose them as Spring properties.
Load all the Spring beans as per the configuration. You can do other operations as well as per project need arises.
@SpringBootApplication Annotation
This annotation is a shortcut of applying 3 annotations in one statement –
@SpringBootConfiguration@SpringBootConfiguration is new annotation in Spring boot 2. Previously, we have been using @Configuration annotation. You can use @Configuration in place of this. Both are same thing.It indicates that a class provides Spring Boot application @Configuration. It simply means that annotated class is a configuration class and shall be scanned for further configurations and bean definitions.
@EnableAutoConfigurationThis annotation is used to enable auto-configuration of the Spring Application Context, attempting to guess and configure beans that you are likely to need. Auto-configuration classes are usually applied based on your classpath and what beans you have defined.Auto-configuration tries to be as intelligent as possible and will back-away as you define more of your own configuration. You can always manually exclude any configuration that you never want to apply using two methods –i) Use excludeName() ii) Using the spring.autoconfigure.exclude property in properties file. e.g.@EnableAutoConfiguration(excludeName = {"multipartResolver","mbeanServer"})Auto-configuration is always applied after user-defined beans have been registered.
@ComponentScanThis annotation provides support parallel with Spring XML’s context:component-scan element.Either basePackageClasses() or basePackages() may be specified to define specific packages to scan. If specific packages are not defined, scanning will occur from the package of the class that declares this annotation.
Run the launch application and check logs
Let’s start running it with the simplest option–running as a Java application. In your IDE, right-click on the application class and run it as Java Application. For getting insight of registered beans, I have added modified the launch application as below.
. ____ _ __ _ _/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \\\/ ___)| |_)| | | | | || (_| | ) ) ) )' |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/:: Spring Boot :: (v2.0.0.RELEASE)2018-04-0213:09:41.100INFO 11452--- [ main] com.fusebes.demo.App : Starting App on FFC15B4E9C5AA with PID 11452(C:\Users\zkpkhua\IDPPaymentTransfers_Integrated\springbootdemo\target\classes started by zkpkhua in C:\Users\zkpkhua\IDPPaymentTransfers_Integrated\springbootdemo)2018-04-0213:09:41.108INFO 11452--- [ main] com.fusebes.demo.App : No active profile set, falling back to defaultprofiles: default2018-04-0213:09:41.222INFO 11452--- [ main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@4450d156: startup date [Mon Apr 0213:09:41IST 2018]; root of context hierarchy2018-04-0213:09:43.474INFO 11452--- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080(http)2018-04-0213:09:43.526INFO 11452--- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]2018-04-0213:09:43.526INFO 11452--- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.282018-04-0213:09:43.748INFO 11452--- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext2018-04-0213:09:43.748INFO 11452--- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 2531ms2018-04-0213:09:43.964INFO 11452--- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Servlet dispatcherServlet mapped to [/]2018-04-0213:09:43.969INFO 11452--- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter'to: [/*]2018-04-02 13:09:43.970 INFO 11452 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]2018-04-02 13:09:43.970 INFO 11452 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*]2018-04-02 13:09:43.970 INFO 11452 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]2018-04-02 13:09:44.480 INFO 11452 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@4450d156: startup date [Mon Apr 02 13:09:41 IST 2018]; root of context hierarchy2018-04-02 13:09:44.627 INFO 11452 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)2018-04-02 13:09:44.630 INFO 11452 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)2018-04-02 13:09:44.681 INFO 11452 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]2018-04-02 13:09:44.682 INFO 11452 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]2018-04-02 13:09:44.747 INFO 11452 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [classorg.springframework.web.servlet.resource.ResourceHttpRequestHandler]2018-04-0213:09:45.002INFO 11452--- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans forJMX exposure on startup2018-04-0213:09:45.070INFO 11452--- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080(http) with context path ''2018-04-0213:09:45.076INFO 11452--- [ main] com.fusebes.demo.App : Started App in 4.609seconds (JVM running for5.263)appbasicErrorControllerbeanNameHandlerMappingbeanNameViewResolvercharacterEncodingFilterconventionErrorViewResolverdefaultServletHandlerMappingdefaultValidatordefaultViewResolverdispatcherServletdispatcherServletRegistrationerrorerrorAttributeserrorPageCustomizererrorPageRegistrarBeanPostProcessorfaviconHandlerMappingfaviconRequestHandlerhandlerExceptionResolverhiddenHttpMethodFilterhttpPutFormContentFilterhttpRequestHandlerAdapterjacksonCodecCustomizerjacksonObjectMapperjacksonObjectMapperBuilderjsonComponentModulelocaleCharsetMappingsCustomizermappingJackson2HttpMessageConvertermbeanExportermbeanServermessageConvertersmethodValidationPostProcessormultipartConfigElementmultipartResolvermvcContentNegotiationManagermvcConversionServicemvcHandlerMappingIntrospectormvcPathMatchermvcResourceUrlProvidermvcUriComponentsContributormvcUrlPathHelpermvcValidatormvcViewResolverobjectNamingStrategyorg.springframework.boot.autoconfigure.AutoConfigurationPackagesorg.springframework.boot.autoconfigure.condition.BeanTypeRegistryorg.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfigurationorg.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfigurationorg.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfigurationorg.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration$StringHttpMessageConverterConfigurationorg.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfigurationorg.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration$MappingJackson2HttpMessageConverterConfigurationorg.springframework.boot.autoconfigure.http.codec.CodecsAutoConfigurationorg.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration$JacksonCodecConfigurationorg.springframework.boot.autoconfigure.info.ProjectInfoAutoConfigurationorg.springframework.boot.autoconfigure.internalCachingMetadataReaderFactoryorg.springframework.boot.autoconfigure.jackson.JacksonAutoConfigurationorg.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$Jackson2ObjectMapperBuilderCustomizerConfigurationorg.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperBuilderConfigurationorg.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperConfigurationorg.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$ParameterNamesModuleConfigurationorg.springframework.boot.autoconfigure.jmx.JmxAutoConfigurationorg.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfigurationorg.springframework.boot.autoconfigure.validation.ValidationAutoConfigurationorg.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfigurationorg.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfigurationorg.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration$TomcatWebServerFactoryCustomizerConfigurationorg.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfigurationorg.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletConfigurationorg.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfigurationorg.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfigurationorg.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfigurationorg.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfigurationorg.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcatorg.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfigurationorg.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfigurationorg.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapterorg.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter$FaviconConfigurationorg.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfigurationorg.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$DefaultErrorViewResolverConfigurationorg.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfigurationorg.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfigurationorg.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration$TomcatWebSocketConfigurationorg.springframework.boot.context.properties.ConfigurationBeanFactoryMetadataorg.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessororg.springframework.context.annotation.internalAutowiredAnnotationProcessororg.springframework.context.annotation.internalCommonAnnotationProcessororg.springframework.context.annotation.internalConfigurationAnnotationProcessororg.springframework.context.annotation.internalRequiredAnnotationProcessororg.springframework.context.event.internalEventListenerFactoryorg.springframework.context.event.internalEventListenerProcessorparameterNamesModulepreserveErrorControllerTargetClassPostProcessorpropertySourcesPlaceholderConfigurerrequestContextFilterrequestMappingHandlerAdapterrequestMappingHandlerMappingresourceHandlerMappingrestTemplateBuilderserver-org.springframework.boot.autoconfigure.web.ServerPropertiesservletWebServerFactoryCustomizersimpleControllerHandlerAdapterspring.http.encoding-org.springframework.boot.autoconfigure.http.HttpEncodingPropertiesspring.info-org.springframework.boot.autoconfigure.info.ProjectInfoPropertiesspring.jackson-org.springframework.boot.autoconfigure.jackson.JacksonPropertiesspring.mvc-org.springframework.boot.autoconfigure.web.servlet.WebMvcPropertiesspring.resources-org.springframework.boot.autoconfigure.web.ResourcePropertiesspring.security-org.springframework.boot.autoconfigure.security.SecurityPropertiesspring.servlet.multipart-org.springframework.boot.autoconfigure.web.servlet.MultipartPropertiesstandardJacksonObjectMapperBuilderCustomizerstringHttpMessageConvertertomcatServletWebServerFactorytomcatServletWebServerFactoryCustomizertomcatWebServerFactoryCustomizerviewControllerHandlerMappingviewResolverwebServerFactoryCustomizerBeanPostProcessorwebsocketContainerCustomizerwelcomePageHandlerMapping
You see how many beans got registered automatically. That’s beauty of spring boot. If you want to dig deeper into why any particular bean got registered? You can see that by putting a debug flag at application startup.
Simply pass -Ddebug=true as VM argument.
Now when you run the application, you will get lots of debug logs having similar information :
CodecsAutoConfiguration.JacksonCodecConfiguration matched:- @ConditionalOnClassfound required class'com.fasterxml.jackson.databind.ObjectMapper'; @ConditionalOnMissingClassdid not find unwanted class(OnClassCondition)CodecsAutoConfiguration.JacksonCodecConfiguration#jacksonCodecCustomizer matched:- @ConditionalOnBean(types: com.fasterxml.jackson.databind.ObjectMapper; SearchStrategy: all) found bean 'jacksonObjectMapper'(OnBeanCondition)DispatcherServletAutoConfiguration.DispatcherServletConfiguration matched:- @ConditionalOnClassfound required class'javax.servlet.ServletRegistration'; @ConditionalOnMissingClassdid not find unwanted class(OnClassCondition)- Default DispatcherServlet did not find dispatcher servlet beans (DispatcherServletAutoConfiguration.DefaultDispatcherServletCondition)DispatcherServletAutoConfiguration.DispatcherServletRegistrationConfiguration matched:- @ConditionalOnClassfound required class'javax.servlet.ServletRegistration'; @ConditionalOnMissingClassdid not find unwanted class(OnClassCondition)- DispatcherServlet Registration did not find servlet registration bean (DispatcherServletAutoConfiguration.DispatcherServletRegistrationCondition).........
Above logs tell why a particular bean was registered into spring context. This information is very useful when you debug the issues with auto configutation.
Similarily, everytime we add a new dependency to a Spring Boot project, Spring Boot auto-configuration automatically tries to configure the beans based on the dependency.
I hope that information discussed above will help you in future while debugging spring boot related issues.
Not very long ago, with the exponential increase in number of libraries and their dependencies, dependency management was becoming very complex task which required good amount of technical expertise to do it correctly. With the introduction of String boot starter templates, you can get a lot of help in identifying the correct dependencies to use in project if you want to use any popular library into your project.
Spring Boot comes with over 50+ different starter modules, which provide ready-to-use integration libraries for many different frameworks, such as database connections that are both relational and NoSQL, web services, social network integration, monitoring libraries, logging, template rendering, and the list just keeps going on.
How starter template work?
Spring Boot starters are templates that contain a collection of all the relevant transitive dependencies that are needed to start a particular functionality. Each starter has a special file, which contains the list of all the provided dependencies Spring provides.
These files can be found inside pom.xml files in respective starter module. e.g.
The spring-boot-starter-data-jpa starter pom file can be found at github.
This tells us that by including spring-boot-starter-data-jpa in our build as a dependency, we will automatically get spring-orm, hibernate-entity-manager and spring-data-jpa. These libraries will provide us all basic things to start writing JPA/DAO code .
So next time when you want to give your project any specific functionality, I will suggest to check for existing starter templates to see if you can use it directly. Ongoing community additions are always on, so this list is already growing and you can contribute to it as well.
Popular templates and their transitive dependencies
I am listing down some very frequently use spring starters and what dependencies they bring along, for information only.
The Spring Boot annotations are mostly placed in org.springframework.boot.autoconfigure and org.springframework.boot.autoconfigure.condition packages. Let’s learn about some frequently used spring boot annotations as well as which work behind the scene.
1. @SpringBootApplication
Spring boot is mostly about auto-configuration. This auto-configuration is done by component scanning i.e. finding all classes in classspath for @Component annotation. It also involve scanning of @Configuration annotation and initialize some extra beans.
@SpringBootApplication annotation enable all able things in one step. It enables the three features:
This annotation enables auto-configuration of the Spring Application Context, attempting to guess and configure beans that we are likely to need based on the presence of predefined classes in classpath.
For example, if we have tomcat-embedded.jar on the classpath, we are likely to want a TomcatServletWebServerFactory.
As this annotation is already included via @SpringBootApplication, so adding it again on main class has no impact. It is also advised to include this annotation only once via @SpringBootApplication.
Auto-configuration classes are regular Spring Configuration beans. They are located using the SpringFactoriesLoader mechanism (keyed against this class). Generally auto-configuration beans are @Conditional beans (most often using @ConditionalOnClass and @ConditionalOnMissingBean annotations).
3. @SpringBootConfiguration
It indicates that a class provides Spring Boot application configuration. It can be used as an alternative to the Spring’s standard @Configuration annotation so that configuration can be found automatically.
Application should only ever include one @SpringBootConfiguration and most idiomatic Spring Boot applications will inherit it from @SpringBootApplication.
The main difference is both annotations is that @SpringBootConfiguration allows configuration to be automatically located. This can be especially useful for unit or integration tests.
4. @ImportAutoConfiguration
It import and apply only the specified auto-configuration classes. The difference between @ImportAutoConfiguration and @EnableAutoConfiguration is that later attempts to configure beans that are found in the classpath during scanning, whereas @ImportAutoConfiguration only runs the configuration classes that we provide in the annotation.
We should use @ImportAutoConfiguration when we don’t want to enable the default auto-configuration.
We can use the @AutoConfigureAfter or @AutoConfigureBefore annotations if our configuration needs to be applied in a specific order (before of after).
If we want to order certain auto-configurations that should not have any direct knowledge of each other, we can also use @AutoConfigureOrder. That annotation has the same semantic as the regular @Order annotation but provides a dedicated order for auto-configuration classes.
All auto-configuration classes generally have one or more @Conditional annotations. It allow to register a bean only when the condition meets. Following are some useful conditional annotations to use.
5.1. @ConditionalOnBean and @ConditionalOnMissingBean
These annotations let a bean be included based on the presence or absence of specific beans.
It’s value attribute is used to specify beans by type or by name. Also the search attribute lets us limit the ApplicationContext hierarchy that should be considered when searching for beans.
Using these annotations at the class level prevents registration of the @Configuration class as a bean if the condition does not match.
In below example, bean JpaTransactionManager will only be loaded if a bean of type JpaTransactionManager is not already defined in the application context.
5.2. @ConditionalOnClass and @ConditionalOnMissingClass
These annotations let configuration classes be included based on the presence or absence of specific classes. Notice that annotation metadata is parsed by using spring ASM module, and even if a class might not be present in runtime – you can still refer to the class in annotation.
We can also use value attribute to refer the real class or the name attribute to specify the class name by using a String value.
Below configuration will create EmbeddedAcmeService only if this class is available in runtime and no other bean with same name is present in application context.
5.3. @ConditionalOnNotWebApplication and @ConditionalOnWebApplication
These annotations let configuration be included depending on whether the application is a “web application” or not. In Spring, a web application is one which meets at least one of below three requirements:
uses a Spring WebApplicationContext
defines a session scope
has a StandardServletEnvironment
5.4. @ConditionalOnProperty
This annotation lets configuration be included based on the presence and value of a Spring Environment property.
For example, if we have different datasource definitions for different environments, we can use this annotation.
This annotation lets configuration be included only when a specific resource is present in the classpath. Resources can be specified by using the usual Spring conventions.
This annotation lets configuration be included based on the result of a SpEL expression. Use this annotation when condition to evaluate is complex one and shall be evaluated as one condition.
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.