An tutorial application using Spring Boot as JPA Relationships back-end.
More details about the codes, please read the online Spring Boot and Spring Data JPA.
Running in
- JDK 1.8 or newer
- Spring Boot 1.5.9.RELEASE or newer
- Gradle 3.5.1 or newer org.projectlombok:lombok
- YAML
- org.projectlombok:lombok 1.16.18 or newer
- org.springframework.security:spring-security-crypto:5.0.0.RELEASE 5.0.0.RELEASE or newer
-
com.google.guava:guava:23.5-jre
-
com.fasterxml.jackson.datatype:jackson-datatype-hibernate5:2.9.3
- 1.0 (Dec 13, 2017)
- Reference Entity Profile
- Build the project gradle build
- Run the application ./gradlew bootRun
Look at the following domain model:
@JsonBackReference
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(updatable = false, nullable = false, name = "user_seq", referencedColumnName = "seq", foreignKey = @ForeignKey(name = "FKEY_USER_SEQ_IN_PROFILE"))
private User host;
- Reference file SpringBootOAuth2AuthorizationServerTutorialApplication.java
username: admin@me.com
password: admin
-- Has authorities: USER, ADMIN
or
username: user@me.com
password: user
-- Has authorities: USER
client id : oneclient
client secret: onesecret
-- Has scopes: read, write
-- Has grant types: authorization_code, refresh_token, implicit, password, client_credentials
or
client id : twoclient
client secret: twosecret
-- Has scopes: read
-- Has grant types: authorization_code, client_credentials
You can use it that create a new account.
- Open file SpringBootOAuth2AuthorizationServerTutorialApplication.java
- And write to your custom users and clients.
@Bean
public CommandLineRunner commandLineRunner(UserRepository userRepository, ClientRepository clientRepository)
{
String yourUsername = "your@email.address";
String yourPassword = new BCryptPasswordEncoder().encode("yourpassword");
String yourClientId = "your_client_id";
String yourClientSecret = new BCryptPasswordEncoder().encode("your_client_secret");
return args ->
{
userRepository.save(new AppUser(3L, yourUsername, yourPassword, "USER,ADMIN"));
clientRepository.save(new AppClient(yourClientId, yourClientSecret, "read,write", "authorization_code,refresh_token,implicit,password,client_credentials"));
};
}
- clientid : id in AppClient entity.
- clientsecret : secret in AppClient entity.
- username : username in AppUser entity.
- password : password in AppUser entity.
$ curl -XPOST "<clientid>:<clientsecret>@localhost:9090/oauth/token" -d "grant_type=password&username=<username>&password=<password>"
$ curl -XPOST "oneclient:onesecret@localhost:9090/oauth/token" -d "grant_type=password&username=user@me.com&password=user"
- access_token : token in order to access resource in ResourceServer
- refresh_token : token in order to get a new access_token in AuthorizationServer
{"access_token":"<access_token>","token_type":"bearer","refresh_token":"<refresh_token>","expires_in":43199,"scope":"read write","jti":"ed68363e-2ced-4466-8c07-894a04cd3250"}
{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTMxNTczNzksInVzZXJfbmFtZSI6InVzZXJAbWUuY29tIiwiYXV0aG9yaXRpZXMiOlsiVVNFUiJdLCJqdGkiOiJlZDY4MzYzZS0yY2VkLTQ0NjYtOGMwNy04OTRhMDRjZDMyNTAiLCJjbGllbnRfaWQiOiJvbmVjbGllbnQiLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXX0.ZFxOMfjVy-z4QkLy20LWvmsClgqpCtIuhlzM9pyw6YUDGgWrIn6QfKFi5OMOmrKFuJvk_IA57aRa27PMAQuHKWKtHryWj71BUqQbWIVt0Cc04ZfBuey5Xy6qIHHvEy-LhaAt4KiX4JnySoLspiuBMgRs0-OCFvAhrO5vEG-Q2svlkivMMEMl3qDgosh4S4IBmmJ-WKckJTOQQ9Zwr3yrSJoNXPDPI_1Nik4jzP2I0rs8jYGuFVG-nst9xd8PRA9JtblAcCjjSwPhV6U72Ue5MdP_vsGXTdSmdlidNeclWqkCYiW3FJQ23LyIo9wT8-ouf9xOXuHn67Tj6C87tV46Ng","token_type":"bearer","refresh_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyQG1lLmNvbSIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSJdLCJhdGkiOiJlZDY4MzYzZS0yY2VkLTQ0NjYtOGMwNy04OTRhMDRjZDMyNTAiLCJleHAiOjE0OTU3MDYxNzksImF1dGhvcml0aWVzIjpbIlVTRVIiXSwianRpIjoiYWIyZTVkNTYtZjQxYi00Zjc2LThjMDktN2Y2NTE0NTc3ODRkIiwiY2xpZW50X2lkIjoib25lY2xpZW50In0.AOtrqPxVmGe0zSkJcDP3-yrYydHLjEkLaJoR47VtfpH2Qhjhf9VhB5r9oF4pAYh9KnSvep5C1BoAIoQslE53DZELLzM4nkxEKY4arGtZkxAjjQWPvdJT5UC8xMVCD8RSmhnB5t0wap5TLr8G78_7uQRLeAxmzwdTtJVBQRUNz_LLU_iokkWZaTbwOlnDLhbAQcR5ZFArwvsxBNlw2YNYOhWhk1jibzBMZvkfv4IP5L_bZyVEKEeCoucJLad_mZvWI9b-6PNTZlzZ3OLxRdRcB6IsKIKWSwP0m9SuQ2tx2MWLeL3b8wCxUAnzjA7ye1LfColsnW2EqY8m3_lMIEoNuw","expires_in":43199,"scope":"read write","jti":"ed68363e-2ced-4466-8c07-894a04cd3250"}
By default Spring Boot applications run on port 9090. But may vary depending on what ports are in use on your machine (check the terminal after entering the ./gradlew bootRun command). If you require to change which port the application runs on by default, add the following to:
server:
port: 9090 # --> change other port via. 9999
- Logback with Spring Boot Logging
- org.projectlombok:lombok 1.16.18 or newer
- org.springframework.security:spring-security-crypto:5.0.0.RELEASE 5.0.0.RELEASE or newer
- 1.0 (Dec 13, 2017)
ROLE (κΆν)
A credential table that is given to a user
μ¬μ©μμκ² λΆμ¬λλ κΆν μ 보 ν μ΄λΈ
ACCEPTANCE (λμμ¬ν)
The table of consent information required for the user
μ¬μ©μμκ² μꡬλλ λμμ¬ν μ 보 ν μ΄λΈ
USER (μ¬μ©μ)
User information table that is the basis of data
λ°μ΄ν°μ κΈ°μ΄κ° λλ μ¬μ©μ μ 보 ν μ΄λΈ
PROFILE (νλ‘ν)
User's detailed information table
μ¬μ©μμ μμΈ μ 보 ν μ΄λΈ
MOBILE (ν΄λμ ν)
User's phone information table
μ¬μ©μμ ν΄λμ ν μ 보 ν μ΄λΈ
Information table that user agrees
μ¬μ©μκ° λμν μ¬ν μ 보 ν μ΄λΈ
Authorization information table granted to user
μ¬μ©μμκ² λΆμ¬λ κΆν μ 보 ν μ΄λΈ
USER (1) --- (1) PROFILE
PROFILE (1) --- (N) MOBILE
USER (N) --- (M) ROLE
PROFILE (N) --- (M) ACCEPTANCE
@JsonManagedReference
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "host")
@JoinColumn(name = "user_seq", referencedColumnName = "seq")
private Profile profile;
@JsonBackReference
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(updatable = false, nullable = false, name = "user_seq", referencedColumnName = "seq", foreignKey = @ForeignKey(name = "FKEY_USER_SEQ_IN_PROFILE"))
private User host;
@JsonManagedReference
@Default
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "holder")
private Collection<Mobile> mobiles = Sets.newHashSet();
@JsonBackReference
@ManyToOne
@JoinColumn(updatable = false, nullable = false, name = "profile_seq", referencedColumnName = "seq", foreignKey = @ForeignKey(name = "FKEY_PROFILE_SEQ_IN_MOBILE"))
private Profile holder;
@Default
@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinTable
(
name = "USER_ROLE",
joinColumns = @JoinColumn(name = "user_seq", referencedColumnName = "seq", foreignKey = @ForeignKey(name = "FKEY_USER_SEQ_IN_USER")),
inverseJoinColumns = @JoinColumn(name = "role_seq", referencedColumnName = "seq", foreignKey = @ForeignKey(name = "FKEY_ROLE_SEQ_IN_ROLE")),
foreignKey = @ForeignKey(name = "FKEY_USER_SEQ__ROLE_SEQ_IN_USER_ROLE")
)
private Collection<Role> roles = Sets.newHashSet();
No attribute, only specified in USER.
Instead, a new table(**USER_ROLE**) is created that connects the two tables.
Check your DBMS.
It is actually 1 : N ( @OneToMany ), not N : M ( @ManyToMany ).
However, since they have the same characteristics as N : M ( @ManyToMany ), they are titled N : M ( @ManyToMany ).
You can also add columns by extending entity.
A new ID table that connects two tables.
@Embeddable // <--- annotation
public class ProfileAcceptanceId implements Serializable // <--- implements Serializable
{
private static final long serialVersionUID = 1L;
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(updatable = false, nullable = false, name = "profile_seq", referencedColumnName = "seq", foreignKey = @ForeignKey(name = "FKEY_PROFILE_SEQ_IN_PROFILE"))
private Profile profile;
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(updatable = false, nullable = false, name = "acceptance_seq", referencedColumnName = "seq", foreignKey = @ForeignKey(name = "FKEY_ACCEPTANCE_SEQ_IN_ACCEPTANCE"))
private Acceptance acceptance;
}
A new Properties(Columns) table.
@Entity
public class ProfileAcceptance
{
@NonNull
@EmbeddedId // <--- annotation
private ProfileAcceptanceId primaryKey; // <--- ID
/* extra properties(columns) */
@Default
@Column(updatable = true, nullable = false, columnDefinition = "BOOLEAN DEFAULT 0 COMMENT 'μμ λμ μ¬λΆ'")
private Boolean accepted = Boolean.FALSE;
...
}
No attribute, only specified in USER.
The following properties(columns) are automatically assigned when saving, even if you do not specify them directly.
- seq - auto_increment by DBMS
- identity - generate by UUID
- updatedAt - apply by Spring Data Annotation
- registedAt - apply by Spring Data Annotation
With identity property(column)
Without identity property(column)
Override toString to ToStringBuilder
public class WithIdentity extends AuditingEntity // <--- extends
or
public class WithoutIdentity extends AuditingWithoutIdentityEntity // <--- extends
- Auto increment sequence
- AuditingWithoutIdentityEntity.java
Unique Identification Keys in the Server
μλ²λ΄μ κ³ μ μλ³ ν€
μλ²λ΄μμ μ¬μ©νλ Primary Key
- UUID
- AuditingEntity.java
private String identity = UUID.randomUUID().toString();
Unique identification key between client and server
ν΄λΌμ΄μΈνΈμ μλ²κ°μ κ³ μ μλ³ ν€
ν΄λΌμ΄μΈνΈμΈ‘μμ μμλ‘ seq λ₯Ό κ°λ¨ν λ³κ²½(μλμ¦κ° μ«μλ₯Ό μ¦κ° λλ μ λ ₯)νμ¬ μλ²μΈ‘μΌλ‘ μμ²ν¨μΌλ‘μ¨ λ°μνλ 보μμ·¨μ½μ¬νμ λ°©μ§νκΈ° μν ν€
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@Column(updatable = false, nullable = false, columnDefinition = "LONG COMMENT 'μλ²λ΄μ κ³ μ μλ³ ν€'")
private Long seq;
...
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(updatable = false, nullable = false, columnDefinition = "BIGINT(10) UNSIGNED COMMENT 'μλ²λ΄μ κ³ μ μλ³ ν€'")
private Long seq;
...
- Hibernate5Module
- ToStringBuilder
- @JsonIdentityInfo
- @JsonManagedReference
@Bean
public ObjectMapper jsonObjectMapper()
{
Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder = Jackson2ObjectMapperBuilder.json();
...
return jackson2ObjectMapperBuilder.build().registerModule(new Hibernate5Module()); // <--- module
}
and
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters)
{
MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new Jackson2HttpMessageConverter(jsonObjectMapper()).getJackson2HttpMessageConverter();
converters.add(jackson2HttpMessageConverter);
}
This project only applies to common AuditingWithoutIdentityEntity.java.
AuditingWithoutIdentityEntity.java
public class AuditingWithoutIdentityEntity
{
...
@Override
public String toString()
{
return new ToStringBuilder(this, ToStringStyle.JSON_STYLE).appendSuper(super.toString()).toString();
}
}
This project only applies to common AuditingWithoutIdentityEntity.java.
AuditingWithoutIdentityEntity.java
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "seq") // <--- annotation
...
public class AuditingWithoutIdentityEntity
{
...
}
@JsonManagedReference
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "host")
@JoinColumn(name = "user_seq", referencedColumnName = "seq")
private Profile profile;
@JsonBackReference
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(updatable = false, nullable = false, name = "user_seq", referencedColumnName = "seq", foreignKey = @ForeignKey(name = "FKEY_USER_SEQ_IN_PROFILE"))
private User host;
- Reference file DataLoader.java.
By default Spring Boot applications run on port 8080. But may vary depending on what ports are in use on your machine (check the terminal after entering the ./gradlew bootRun command). If you require to change which port the application runs on by default, add the following to:
server:
context-path: /
port: 8080 # ---> change other port via. 9999
http://localhost:8080/swagger-ui.html
spring:
h2:
console:
path: /h2-console
enabled: true
http://localhost:8080/h2-console
Configurations
JsonObjectMapper
ToStringBuilder
JsonManagedReference with JsonBackReference
warumono - warumono.for.develop@gmail.com