最近這兩年最流行的java框架也屬SpringBoot了,早在前幾年我一直用NinjaFramwork這個java框架,也是很是優秀,不過最近在面試各家公司的過程當中最爲流行的仍是SpringBoot了,所以也學一下吧javascript
https://spring.io/projects/spring-boot#samples 這是官方教程,秒殺一切教程css
https://github.com/spring-projects/spring-boot 這是官方教程,秒殺一切教程html
以上是官方demo,建議學習就學官方的,接下來我把學習過程以及整個技術選型記錄下來,若有建議或更正但願你們能提出.前端
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------java
1:爲何使用SpringBoot而不使用Ninjaframwork?mysql
沒有爲何,你們都在用而已,Ninjaframwork也很是好,官網Ninja - full stack web framework for Java -react
Spring Boot的核心思想就是約定大於配置,一切自動完成。採用Spring Boot能夠大大的簡化你的開發模式,全部你想集成的經常使用框架,它都有對應的組件支持linux
而我用了接近4年的Ninjaframwork也是如此優秀,本着學習的原則選擇一下springbootgit
2:初步瞭解一下springboot吧。(官方已經很詳細的介紹,但仍是想看一下國內一些企業或我的的想法)github
參考http://www.最代碼(改成英文便可).com/blog/3545313038879744.htm (博客園居然禁止)
http://www.ityouknow.com/springboot/2016/01/06/springboot(%E4%B8%80)-%E5%85%A5%E9%97%A8%E7%AF%87.html
java -jar app.jar --spring.profiles.active=dev 啓動測試環境的配置文件
3:學習構建一個SpringBoot,使用maven仍是gradle構建項目呢?
用了接近4年的maven 本着學習的原則,直接換gradle ,官方介紹參考https://gradle.org/
國內人士分析參考https://blog.csdn.net/zguoshuaiiii/article/details/78376331
4:Spring Data JPA hibernate 什麼關係?
參考http://www.cnblogs.com/xiaoheike/p/5150553.html
https://blog.csdn.net/linux__xu/article/details/75574342
https://blog.csdn.net/xihuanyuye/article/details/81201441
https://blog.csdn.net/qq897958555/article/details/53208002
5 項目使用什麼數據庫鏈接呢? 不用想太多,直接postgresql 便可
6 接下來 就要登錄系統,那麼 Shiro和Spring Security對比 ,參考:原文:https://blog.csdn.net/liyuejin/article/details/77838868
Shiro簡介
Apache Shiro是Java的一個安全框架。目前,使用Apache Shiro的人愈來愈多,由於它至關簡單,對比Spring Security,可能沒有Spring Security作的功能強大,可是在實際工做時可能並不須要那麼複雜的東西,因此使用小而簡單的Shiro就足夠了。對於它倆到底哪一個好,這個沒必要糾結,能更簡單的解決項目問題就行了。
Shiro架構與功能介紹
1.認證與受權相關基本概念
兩個基本的概念
安全實體:系統須要保護的具體對象數據
權限:系統相關的功能操做,例如基本的CRUD
Authentication:身份認證/登陸,驗證用戶是否是擁有相應的身份;
Authorization:受權,即權限驗證,驗證某個已認證的用戶是否擁有某個權限;即判斷用戶是否能作事情,常見的如:驗證某個用戶是否擁有某個角色。或者細粒度的驗證某個用戶對某個資源是否具備某個權限;
Session Manager:會話管理,即用戶登陸後就是一次會話,在沒有退出以前,它的全部信息都在會話中;會話能夠是普通JavaSE環境的,也能夠是如Web環境的;
Cryptography:加密,保護數據的安全性,如密碼加密存儲到數據庫,而不是明文存儲;
Web Support:Web支持,能夠很是容易的集成到Web環境;
Caching:緩存,好比用戶登陸後,其用戶信息、擁有的角色/權限沒必要每次去查,這樣能夠提升效率;
Concurrency:shiro支持多線程應用的併發驗證,即如在一個線程中開啓另外一個線程,能把權限自動傳播過去;
Testing:提供測試支持;
Run As:容許一個用戶僞裝爲另外一個用戶(若是他們容許)的身份進行訪問;
Remember Me:記住我,這個是很是常見的功能,即一次登陸後,下次再來的話不用登陸了。
2.Shiro四大核心功能:Authentication,Authorization,Cryptography,Session Management
Java安全框架Shiro和Spring Security對比
Shiro架構
3.Shiro三個核心組件:Subject, SecurityManager 和 Realms.
Subject:主體,表明了當前「用戶」,這個用戶不必定是一個具體的人,與當前應用交互的任何東西都是Subject,如網絡爬蟲,機器人等;即一個抽象概念;全部Subject都綁定到SecurityManager,與Subject的全部交互都會委託給SecurityManager;能夠把Subject認爲是一個門面;SecurityManager纔是實際的執行者;
SecurityManager:安全管理器;即全部與安全有關的操做都會與SecurityManager交互;且它管理着全部Subject;能夠看出它是Shiro的核心,它負責與後邊介紹的其餘組件進行交互,若是學習過SpringMVC,你能夠把它當作DispatcherServlet前端控制器;
Realm:域,Shiro從從Realm獲取安全數據(如用戶、角色、權限),就是說SecurityManager要驗證用戶身份,那麼它須要從Realm獲取相應的用戶進行比較以肯定用戶身份是否合法;也須要從Realm獲得用戶相應的角色/權限進行驗證用戶是否能進行操做;能夠把Realm當作DataSource,即安全數據源。
Spring Security簡介
Spring Security是一個可以爲基於Spring的企業應用系統提供聲明式的安全訪問控制解決方案的安全框架。它提供了一組能夠在Spring應用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反轉Inversion of Control ,DI:Dependency Injection 依賴注入)和AOP(面向切面編程)功能,爲應用系統提供聲明式的安全訪問控制功能,減小了爲企業系統安全控制編寫大量重複代碼的工做。它是一個輕量級的安全框架,它確保基於Spring的應用程序提供身份驗證和受權支持。它與Spring MVC有很好地集成,並配備了流行的安全算法實現捆綁在一塊兒。安全主要包括兩個操做「認證」與「驗證」(有時候也會叫作權限控制)。「認證」是爲用戶創建一個其聲明的角色的過程,這個角色能夠一個用戶、一個設備或者一個系統。「驗證」指的是一個用戶在你的應用中可以執行某個操做。在到達受權判斷以前,角色已經在身份認證過程當中創建了。
它的設計是基於框架內大範圍的依賴的,能夠被劃分爲如下幾塊。
Web/Http 安全:這是最複雜的部分。經過創建 filter 和相關的 service bean 來實現框架的認證機制。當訪問受保護的 URL 時會將用戶引入登陸界面或者是錯誤提示界面。
業務對象或者方法的安全:控制方法訪問權限的。
AuthenticationManager:處理來自於框架其餘部分的認證請求。
AccessDecisionManager:爲 Web 或方法的安全提供訪問決策。會註冊一個默認的,可是咱們也能夠經過普通 bean 註冊的方式使用自定義的 AccessDecisionManager。
AuthenticationProvider:AuthenticationManager 是經過它來認證用戶的。
UserDetailsService:跟 AuthenticationProvider 關係密切,用來獲取用戶信息的。
Java安全框架Shiro和Spring Security對比
Shiro和Spring Security比較
Shiro比Spring更容易使用,實現和最重要的理解
Spring Security更加知名的惟一緣由是由於品牌名稱
「Spring」以簡單而聞名,但諷刺的是不少人發現安裝Spring Security很難
然而,Spring Security卻有更好的社區支持
Apache Shiro在Spring Security處理密碼學方面有一個額外的模塊
Spring-security 對spring 結合較好,若是項目用的springmvc ,使用起來很方便。可是若是項目中沒有用到spring,那就不要考慮它了。
Shiro 功能強大、且 簡單、靈活。是Apache 下的項目比較可靠,且不跟任何的框架或者容器綁定,能夠獨立運行
---------------------
Spring Security如何使用,先在你的項目pom.xml文件中聲明依賴。
<dependency>
<!-- 因爲我使用的spring boot因此我是引入spring-boot-starter-security並且我使用了spring io因此不須要填寫依賴的版本號 --> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
而後建立一個類並繼承WebSecurityConfigurerAdapter這個方法,並在之類中重寫configure的3個方法,其中3個方法中參數包括爲HttpSecurity(HTTP請求安全處理),AuthenticationManagerBuilder(身份驗證管理生成器)和WebSecurity(WEB安全)。
public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth){ super.configure(auth); } @Override protected void configure(HttpSecurity http){ super.configure(http); } @Override protected void configure(WebSecurity web){ super.configure(web); } }
接下來咱們先看看protected void configure(HttpSecurity http)這個方法提供了一個默認的配置。
protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .and() .httpBasic(); }
http.authorizeRequests()其中這裏的意思是指經過authorizeRequests()方法來開始請求權限配置。
而接着的.anyRequest().authenticated()是對http全部的請求必須經過受權認證才能夠訪問。
而and()是返回一個securityBuilder對象,formLogin()和httpBasic()是受權的兩種方式。
固然這些界面都是spring security原生的界面,咱們也能夠自定義咱們的formLogin頁面!
protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() .and() .formLogin() //指定登陸頁的路徑 .loginPage("/login") //必須容許全部用戶訪問咱們的登陸頁(例如未驗證的用戶,不然驗證流程就會進入死循環) //這個formLogin().permitAll()方法容許全部用戶基於表單登陸訪問/login這個page。 .permitAll(); }
提示一下,這個自定義表單登陸的自定義頁面中的登陸名參數必須被命名爲username
密碼參數必須被命名爲password。
而接下來當咱們須要對某些開放的url,給與任何人訪問的時候,咱們應該如何設置呢?答案很簡單咱們先看着代碼慢慢深刻!
protected void configure(HttpSecurity http) throws Exception { http //http.authorizeRequests()方法有多個子節點,每一個macher按照他們的聲明順序執行 .authorizeRequests() //咱們指定任何用戶均可以訪問多個URL的模式。 //任何用戶均可以訪問以"/resources/","/signup", 或者 "/about"開頭的URL。 .antMatchers("/resources/**", "/signup", "/about").permitAll() //以 "/admin/" 開頭的URL只能讓擁有 "ROLE_ADMIN"角色的用戶訪問。 //請注意咱們使用 hasRole 方法,沒有使用 "ROLE_" 前綴。 .antMatchers("/admin/**").hasRole("ADMIN") //任何以"/db/" 開頭的URL須要同時具備 "ROLE_ADMIN" 和 "ROLE_DBA"權限的用戶才能夠訪問。 //和上面同樣咱們的 hasRole 方法也沒有使用 "ROLE_" 前綴。 .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") //任何以"/db/" 開頭的URL只須要擁有 "ROLE_ADMIN" 和 "ROLE_DBA"其中一個權限的用戶才能夠訪問。 //和上面同樣咱們的 hasRole 方法也沒有使用 "ROLE_" 前綴。 .antMatchers("/db/**").hasAnyRole("ADMIN", "DBA") //還沒有匹配的任何URL都要求用戶進行身份驗證 .anyRequest().authenticated() .and() // ... .formLogin(); }
咱們能夠在authorizeRequests() 後定義多個antMatchers()配置器來控制不一樣的url接受不一樣權限的用戶訪問,而其中permitAll() 方法是運行全部權限用戶包含匿名用戶訪問。
而hasRole("權限")則是容許這個url給與參數中相等的權限訪問。
access("hasRole('權限') and hasRole('權限')") 是指容許訪問這個url必須同時擁有參數中多個身份權限才能夠訪問。
hasAnyRole("ADMIN", "DBA")是指容許訪問這個url必須同時擁有參數中多個身份權限中的一個就能夠訪問該url。
咱們接下來就簡單的定製一下登陸登出行爲!
protected void configure(HttpSecurity http) throws Exception { http //經過formlogin方法登陸,並設置登陸url爲/api/user/login .formLogin().loginPage("/api/user/login") //指定登陸成功後跳轉到/index頁面 .defaultSuccessUrl("/index") //指定登陸失敗後跳轉到/login?error頁面 .failureUrl("/login?error") .permitAll() .and() //開啓cookie儲存用戶信息,並設置有效期爲14天,指定cookie中的密鑰 .rememberMe().tokenValiditySeconds(1209600).key("mykey") .and() .logout() //指定登出的url .logoutUrl("/api/user/logout") //指定登場成功以後跳轉的url .logoutSuccessUrl("/index") .permitAll(); }
public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override //重寫了configure參數爲AuthenticationManagerBuilder的方法 protected void configure(AuthenticationManagerBuilder auth){ //並根據傳入的AuthenticationManagerBuilder中的userDetailsService方法來接收咱們自定義的認證方法。 //且該方法必需要實現UserDetailsService這個接口。 auth.userDetailsService(new myUserDetailsService()) //密碼使用BCryptPasswordEncoder()方法驗證,由於這裏使用了BCryptPasswordEncoder()方法驗證。因此在註冊用戶的時候在接收前臺明文密碼以後也須要使用BCryptPasswordEncoder().encode(明文密碼)方法加密密碼。 .passwordEncoder(new BCryptPasswordEncoder());; } @Override protected void configure(HttpSecurity http){ super.configure(http); } @Override protected void configure(WebSecurity web){ super.configure(web); } }
新建myUserDetailsService方法並實現UserDetailsService這個接口
@Component public class myUserDetailsService implements UserDetailsService { @Autowired //因爲是演示這裏就再也不建立service層了,直接注入UserRepository。 private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException { //查詢帳號是否存在,是就返回一個UserDetails的對象,否就拋出異常! User user = userRepository.findByName(userName); if (user == null) { throw new UsernameNotFoundException("UserName " + userName + " not found"); } return new SecurityUser(user); } }
基本的認證邏輯就到這裏了,對於有另外的業務需求均可以在自定義的myUserDetailsService中處理完成!
@EnableGlobalAuthentication public class SecurityConfig extends WebSecurityConfigurerAdapter{ @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .and() .csrf().disable() .authorizeRequests() .antMatchers("/admin").permitAll() //使用自定義受權策略 .anyRequest().access("@mySecurity.check(authentication,request)"); } }
新建MySecurity類
@Component("mySecurity") public class MySecurity(){ //這裏應該注入用戶和該用戶所擁有的權限(權限在登陸成功的時候已經緩存起來,當須要訪問該用戶的權限是,直接從緩存取出!),而後驗證該請求是否有權限,有就返回true,不然則返回false不容許訪問該Url。 //並且這裏還傳入了request,我也可使用request獲取該次請求的類型。 //根據restful風格咱們可使用它來控制咱們的權限,例如當這個請求是post請求,證實該請求是向服務器發送一個新建資源請求,咱們可使用request.getMethod()來獲取該請求的方式,而後在配合角色所容許的權限路徑進行判斷和受權操做! public boolean check(Authentication authentication, HttpServletRequest request){ //若是能獲取到Principal對象不爲空證實,受權已經經過 Object principal = authentication.getPrincipal(); if(principal != null && principal instanceof UserDetails){ //獲取請求登陸的url System.out.println(((UserDetails)principal).getAuthorities()) ; return true; } return false; } }
-----------------------------------------------------------------------------------------------------------------------
參考https://blog.csdn.net/xuemengrui12/article/details/80525227
先看下二者的接口代碼:
@NoRepositoryBean
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {
<S extends T> S save(S var1);
<S extends T> Iterable<S> save(Iterable<S> var1);
T findOne(ID var1);
boolean exists(ID var1);
Iterable<T> findAll();
Iterable<T> findAll(Iterable<ID> var1);
long count();
void delete(ID var1);
void delete(T var1);
void delete(Iterable<? extends T> var1);
void deleteAll();
}
@NoRepositoryBean
public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
List<T> findAll();
List<T> findAll(Sort var1);
List<T> findAll(Iterable<ID> var1);
<S extends T> List<S> save(Iterable<S> var1);
void flush();
<S extends T> S saveAndFlush(S var1);
void deleteInBatch(Iterable<T> var1);
void deleteAllInBatch();
T getOne(ID var1);
<S extends T> List<S> findAll(Example<S> var1);
<S extends T> List<S> findAll(Example<S> var1, Sort var2);
}
看一下他們的繼承關係
注意下二者的save方法的不一樣,JpaRepository 中的save方法實現源碼:
@Transactional
public <S extends T> List<S> save(Iterable<S> entities) {
List<S> result = new ArrayList<S>();
if (entities == null) {
return result;
}
for (S entity : entities) {
result.add(save(entity));
}
return result;
}
CrudRepository 中的save方法源代碼
@Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);//是新的就插入
return entity;
} else {
return em.merge(entity); //不是新的merge
}
}
由源碼可知CrudRepository 中的save方法是至關於merge+save ,它會先判斷記錄是否存在,若是存在則更新,不存在則插入記錄。唉,仍是須要多看源碼啊
參考:
https://blog.csdn.net/zgf19930504/article/details/50537222
https://blog.csdn.net/strive_peter/article/details/76276758
https://blog.csdn.net/hikeboy/article/details/58597053
http://makaidong.com/youhan26/1/976_10156939.html
到底用哪個 參考:https://www.ibm.com/developerworks/cn/opensource/os-cn-spring-jpa/index.html
參考https://www.callicoder.com/spring-boot-rest-api-tutorial-with-mysql-jpa-hibernate/
Spring Boot has taken Spring framework to the next level. It has drastically reduced the configuration and setup time required for spring projects.
You can setup a project with almost zero configuration and start building the things that actually matter to your application.
If you are new to Spring boot and want to get started with it quickly, then this blog post is for you.
In this post, we’ll build a Restful CRUD API for a simple note-taking application. A Note can have a title and some content. We’ll first build the apis to create, retrieve, update and delete a Note, and then test them using postman.
So, Let’s get started!
Spring Boot provides a web tool called Spring Initializer to bootstrap an application quickly. Just go to http://start.spring.io and follow the steps below to generate a new project.
Step 1 : Click Switch to full version on http://start.spring.io page.
Step 2 : Enter the details as follows -
Once all the details are entered, click Generate Project to generate and download your project. Spring Initializer will generate the project with the details you have entered and download a zip file with all the project folders.
Next, Unzip the downloaded zip file and import it into your favorite IDE.
Following is the directory structure of our Note taking application -
Let’s understand the details of some of the important files and directories -
1. EasyNotesApplication
This is the main entry point of our Spring Boot application.
package com.example.easynotes; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class EasyNotesApplication { public static void main(String[] args) { SpringApplication.run(EasyNotesApplication.class, args); } }
It contains a simple annotation called @SpringBootApplication
which is a combination of the following more specific spring annotations -
@Configuration : Any class annotated with @Configuration
annotation is bootstrapped by Spring and is also considered as a source of other bean definitions.
@EnableAutoConfiguration : This annotation tells Spring to automatically configure your application based on the dependencies that you have added in the pom.xml
file.
For example, If spring-data-jpa
or spring-jdbc
is in the classpath, then it automatically tries to configure a DataSource
by reading the database properties from application.properties
file.
@ComponentScan : It tells Spring to scan and bootstrap other components defined in the current package (com.example.easynotes) and all the sub-packages.
The main()
method calls Spring Boot’s SpringApplication.run()
method to launch the application.
2. resources/
This directory, as the name suggests, is dedicated to all the static resources, templates and property files.
resources/static - contains static resources such as css, js and images.
resources/templates - contains server-side templates which are rendered by Spring.
resources/application.properties - This file is very important. It contains application-wide properties. Spring reads the properties defined in this file to configure your application. You can define server’s default port, server’s context path, database URLs etc, in this file.
You can refer this page for common application properties used in Spring Boot.
3. EasyNotesApplicationTests - Define unit and integration tests here.
4. pom.xml - contains all the project dependencies
As I pointed out earlier, Spring Boot tries to auto-configure a DataSource
if spring-data-jpa
is in the classpath by reading the database configuration from application.properties
file.
So, we just have to add the configuration and Spring Boot will take care of the rest.
Open application.properties
file and add the following properties to it.
## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties) spring.datasource.url = jdbc:mysql://localhost:3306/notes_app?useSSL=false spring.datasource.username = root spring.datasource.password = root ## Hibernate Properties # 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
You will need to create a database named notes_app in MySQL, and change the spring.datasource.username
& spring.datasource.password
properties as per your MySQL installation.
In the above properties file, the last two properties are for hibernate. Spring Boot uses Hibernate as the default JPA implementation.
The property spring.jpa.hibernate.ddl-auto
is used for database initialization. I’ve used the value 「update」 for this property.
It does two things -
When you define a domain model, a table will automatically be created in the database and the fields of the domain model will be mapped to the corresponding columns in the table.
Any change to the domain model will also trigger an update to the table. For example, If you change the name or type of a field, or add another field to the model, then all these changes will be reflected in the mapped table as well.
Using update for spring.jpa.hibernate.ddl-auto
property is fine for development. But, For production, You should keep the value of this property to 「validate」, and use a database migration tool like Flyway for managing changes in the database schema.
All right! Let’s now create the Note
model. Our Note
model has following fields -
id
: Primary Key with Auto Increment.title
: The title of the Note. (NOT NULL field)content
: Note’s content. (NOT NULL field)createdAt
: Time at which the Note
was created.updatedAt
: Time at which the Note
was updated.Now, let’s see how we can model this in Spring. Create a new package called model
inside com.example.easynotes
and add a class named Note.java
with following contents -
package com.example.easynotes.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.NotBlank; import java.util.Date; @Entity @Table(name = "notes") @EntityListeners(AuditingEntityListener.class) @JsonIgnoreProperties(value = {"createdAt", "updatedAt"}, allowGetters = true) public class Note implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotBlank private String title; @NotBlank private String content; @Column(nullable = false, updatable = false) @Temporal(TemporalType.TIMESTAMP) @CreatedDate private Date createdAt; @Column(nullable = false) @Temporal(TemporalType.TIMESTAMP) @LastModifiedDate private Date updatedAt; // Getters and Setters ... (Omitted for brevity) }
All your domain models must be annotated with @Entity
annotation. It is used to mark the class as a persistent Java class.
@Table
annotation is used to provide the details of the table that this entity will be mapped to.
@Id
annotation is used to define the primary key.
@GeneratedValue
annotation is used to define the primary key generation strategy. In the above case, we have declared the primary key to be an Auto Increment
field.
@NotBlank
annotation is used to validate that the annotated field is not null
or empty.
@Column
annotation is used to define the properties of the column that will be mapped to the annotated field. You can define several properties like name, length, nullable, updateable etc.
By default, a field named createdAt
is mapped to a column named created_at
in the database table. i.e. all camel cases are replaced with underscores.
If you want to map the field to a different column, you can specify it using -
@Column(name = "created_on") private String createdAt;
@Temporal
annotation is used with java.util.Date
and java.util.Calendar
classes. It converts the date and time values from Java Object to compatible database type and vice versa.
@JsonIgnoreProperties
annotation is a Jackson annotation. Spring Boot uses Jackson for Serializing and Deserializing Java objects to and from JSON.
This annotation is used because we don’t want the clients of the rest api to supply the createdAt
and updatedAt
values. If they supply these values then we’ll simply ignore them. However, we’ll include these values in the JSON response.
In our Note
model we have annotated createdAt
and updatedAt
fields with @CreatedDate
and @LastModifiedDate
annotations respectively.
Now, what we want is that these fields should automatically get populated whenever we create or update an entity.
To achieve this, we need to do two things -
1. Add Spring Data JPA’s AuditingEntityListener
to the domain model.
We have already done this in our Note
model with the annotation @EntityListeners(AuditingEntityListener.class)
.
2. Enable JPA Auditing in the main application.
Open EasyNotesApplication.java
and add @EnableJpaAuditing
annotation.
package com.example.easynotes; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @SpringBootApplication @EnableJpaAuditing public class EasyNotesApplication { public static void main(String[] args) { SpringApplication.run(EasyNotesApplication.class, args); } }
The next thing we’re gonna do is create a repository to access Note’s data from the database.
Well, Spring Data JPA has got us covered here. It comes with a JpaRepository
interface which defines methods for all the CRUD operations on the entity, and a default implementation of JpaRepository
called SimpleJpaRepository
.
Cool! Let’s create the repository now. First, Create a new package called repository
inside the base package com.example.easynotes
. Then, create an interface called NoteRepository
and extend it from JpaRepository
-
package com.example.easynotes.repository; import com.example.easynotes.model.Note; import org.springframework.data.jpa.repository.JpaRepository; @Repository public interface NoteRepository extends JpaRepository<Note, Long> { }
Note that, we have annotated the interface with @Repository
annotation. This tells Spring to bootstrap the repository during component scan.
Great! That is all you have to do in the repository layer. You will now be able to use JpaRepository’s methods like save()
, findOne()
, findAll()
, count()
, delete()
etc.
You don’t need to implement these methods. They are already implemented by Spring Data JPA’s SimpleJpaRepository
. This implementation is plugged in by Spring automatically at runtime.
Checkout all the methods available from SimpleJpaRepository’s documentation.
Spring Data JPA has a bunch of other interesting features like Query methods (dynamically creating queries based on method names), Criteria API, Specifications, QueryDsl etc.
I strongly recommend you to checkout the Spring Data JPA’s documentation to learn more.
We’ll define the Rest APIs for creating, retrieving, updating, and deleting a Note
in the next section.
The APIs will throw a ResourceNotFoundException
whenever a Note
with a given id
is not found in the database.
Following is the definition of ResourceNotFoundException
. (I’ve created a package named exception
inside com.example.easynotes
to store this exception class) -
package com.example.easynotes.exception; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; @ResponseStatus(value = HttpStatus.NOT_FOUND) public class ResourceNotFoundException extends RuntimeException { private String resourceName; private String fieldName; private Object fieldValue; public ResourceNotFoundException( String resourceName, String fieldName, Object fieldValue) { super(String.format("%s not found with %s : '%s'", resourceName, fieldName, fieldValue)); this.resourceName = resourceName; this.fieldName = fieldName; this.fieldValue = fieldValue; } public String getResourceName() { return resourceName; } public String getFieldName() { return fieldName; } public Object getFieldValue() { return fieldValue; } }
Notice the use of @ResponseStatus
annotation in the above exception class. This will cause Spring boot to respond with the specified HTTP status code whenever this exception is thrown from your controller.
The Final Step - We’ll now create the REST APIs for creating, retrieving, updating and deleting a Note.
First, create a new package controller
inside com.example.easynotes
. Then, create a new class NoteController.java
with the following contents -
package com.example.easynotes.controller; import com.example.easynotes.exception.ResourceNotFoundException; import com.example.easynotes.model.Note; import com.example.easynotes.repository.NoteRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; import java.util.List; @RestController @RequestMapping("/api") public class NoteController { @Autowired NoteRepository noteRepository; // Get All Notes // Create a new Note // Get a Single Note // Update a Note // Delete a Note }
@RestController
annotation is a combination of Spring’s @Controller
and @ResponseBody
annotations.
The @Controller
annotation is used to define a controller and the @ResponseBody
annotation is used to indicate that the return value of a method should be used as the response body of the request.
@RequestMapping("/api")
declares that the url for all the apis in this controller will start with /api
.
Let’s now look at the implementation of all the apis one by one.
// Get All Notes @GetMapping("/notes") public List<Note> getAllNotes() { return noteRepository.findAll(); }
The above method is pretty straightforward. It calls JpaRepository’s findAll()
method to retrieve all the notes from the database and returns the entire list.
Also, The @GetMapping("/notes")
annotation is a short form of @RequestMapping(value="/notes", method=RequestMethod.GET)
.
// Create a new Note @PostMapping("/notes") public Note createNote(@Valid @RequestBody Note note) { return noteRepository.save(note); }
The @RequestBody
annotation is used to bind the request body with a method parameter.
The @Valid
annotation makes sure that the request body is valid. Remember, we had marked Note’s title and content with @NotBlank
annotation in the Note
model?
If the request body doesn’t have a title or a content, then spring will return a 400 BadRequest
error to the client.
// Get a Single Note @GetMapping("/notes/{id}") public Note getNoteById(@PathVariable(value = "id") Long noteId) { return noteRepository.findById(noteId) .orElseThrow(() -> new ResourceNotFoundException("Note", "id", noteId)); }
The @PathVariable
annotation, as the name suggests, is used to bind a path variable with a method parameter.
In the above method, we are throwing a ResourceNotFoundException
whenever a Note
with the given id is not found.
This will cause Spring Boot to return a 404 Not Found error to the client (Remember, we had added a @ResponseStatus(value = HttpStatus.NOT_FOUND)
annotation to the ResourceNotFoundException
class).
// Update a Note @PutMapping("/notes/{id}") public Note updateNote(@PathVariable(value = "id") Long noteId, @Valid @RequestBody Note noteDetails) { Note note = noteRepository.findById(noteId) .orElseThrow(() -> new ResourceNotFoundException("Note", "id", noteId)); note.setTitle(noteDetails.getTitle()); note.setContent(noteDetails.getContent()); Note updatedNote = noteRepository.save(note); return updatedNote; }
// Delete a Note @DeleteMapping("/notes/{id}") public ResponseEntity<?> deleteNote(@PathVariable(value = "id") Long noteId) { Note note = noteRepository.findById(noteId) .orElseThrow(() -> new ResourceNotFoundException("Note", "id", noteId)); noteRepository.delete(note); return ResponseEntity.ok().build(); }
We’ve successfully built all the apis for our application. Let’s now run the app and test the apis.
Just go to the root directory of the application and type the following command to run it -
$ mvn spring-boot:run
The application will start at Spring Boot’s default tomcat port 8080.
Great! Now, It’s time to test our apis using postman.
POST /api/notes
APIGET /api/notes
APIGET /api/notes/{noteId}
APIPUT /api/notes/{noteId}
APIDELETE /api/notes/{noteId}
APIThe application that we built in this article had only one domain model. If you want to learn how to build REST APIs in an application with more than one domain models exhibiting a one-to-many relationship between each other, then I highly recommend you to check out the following article -
Spring Boot, JPA, Hibernate One-To-Many mapping example
Also, Go through the following article to learn how to build a full stack application with authentication and authorization using Spring Boot, Spring Security and React -
Spring Boot + Spring Security + JWT + MySQL + React Full Stack Polling App - Part 1
Congratulations folks! We successfully built a Restful CRUD API using Spring Boot, Mysql, Jpa and Hibernate.
You can find the source code for this tutorial on my github repository. Feel free to clone the repository and build upon it.
Thank you for reading. Please ask any questions in the comment section below.