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