Skip to content

Commit 9eaa8df

Browse files
authored
Micronaut Data JDBC Many to Many guide (#1610)
1 parent b4e7702 commit 9eaa8df

File tree

20 files changed

+680
-0
lines changed

20 files changed

+680
-0
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2017-2025 original authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example.micronaut;
17+
18+
import io.micronaut.core.annotation.Nullable;
19+
import io.micronaut.data.annotation.GeneratedValue;
20+
import io.micronaut.data.annotation.Id;
21+
import io.micronaut.data.annotation.MappedEntity;
22+
import jakarta.validation.constraints.NotBlank;
23+
24+
@MappedEntity // <1>
25+
public record Role(@Nullable
26+
@Id // <2>
27+
@GeneratedValue // <3>
28+
Long id,
29+
30+
@NotBlank // <4>
31+
String authority) {
32+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2017-2025 original authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example.micronaut;
17+
18+
import io.micronaut.core.annotation.Introspected;
19+
import io.micronaut.core.annotation.NonNull;
20+
import io.micronaut.core.annotation.Nullable;
21+
import jakarta.validation.constraints.NotBlank;
22+
import java.util.List;
23+
24+
@Introspected // <1>
25+
public record User(@NonNull @NotBlank Long id,
26+
@NonNull @NotBlank String username,
27+
@Nullable List<String> authorities) {
28+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2017-2025 original authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example.micronaut;
17+
18+
import io.micronaut.core.annotation.Nullable;
19+
import io.micronaut.data.annotation.GeneratedValue;
20+
import io.micronaut.data.annotation.Id;
21+
import io.micronaut.data.annotation.MappedEntity;
22+
import jakarta.validation.constraints.NotBlank;
23+
24+
@MappedEntity("user") // <1>
25+
public record UserEntity(@Nullable
26+
@Id // <2>
27+
@GeneratedValue // <3>
28+
Long id,
29+
30+
@NotBlank // <4>
31+
String username
32+
) {
33+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2017-2025 original authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example.micronaut;
17+
18+
import io.micronaut.data.annotation.EmbeddedId;
19+
import io.micronaut.data.annotation.MappedEntity;
20+
21+
@MappedEntity // <1>
22+
public class UserRole {
23+
24+
@EmbeddedId // <2>
25+
private final UserRoleId id;
26+
27+
public UserRole(UserRoleId id) {
28+
this.id = id;
29+
}
30+
31+
public UserRole(UserEntity user, Role role) {
32+
this(new UserRoleId(user, role));
33+
}
34+
35+
public UserRoleId getId() {
36+
return id;
37+
}
38+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2017-2025 original authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example.micronaut;
17+
18+
import io.micronaut.data.annotation.Embeddable;
19+
import io.micronaut.data.annotation.Relation;
20+
21+
import java.util.Objects;
22+
23+
@Embeddable // <1>
24+
public class UserRoleId {
25+
26+
@Relation(value = Relation.Kind.MANY_TO_ONE) // <2>
27+
private final UserEntity user;
28+
29+
@Relation(value = Relation.Kind.MANY_TO_ONE) // <2>
30+
private final Role role;
31+
32+
public UserRoleId(UserEntity user, Role role) {
33+
this.user = user;
34+
this.role = role;
35+
}
36+
37+
@Override
38+
public boolean equals(Object o) {
39+
if (this == o) {
40+
return true;
41+
}
42+
if (o == null || getClass() != o.getClass()) {
43+
return false;
44+
}
45+
UserRoleId userRoleId = (UserRoleId) o;
46+
return role.id().equals(userRoleId.getRole().id()) &&
47+
user.id().equals(userRoleId.getUser().id());
48+
}
49+
50+
@Override
51+
public int hashCode() {
52+
return Objects.hash(role.id(), user.id());
53+
}
54+
55+
public UserEntity getUser() {
56+
return user;
57+
}
58+
59+
public Role getRole() {
60+
return role;
61+
}
62+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<databaseChangeLog
4+
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
5+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
6+
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
7+
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
8+
<changeSet id="01" author="username">
9+
<createTable tableName="user">
10+
<column name="id" type="BIGINT" autoIncrement="true">
11+
<constraints primaryKey="true" primaryKeyName="pk_user" nullable="false"/>
12+
</column>
13+
<column name="username" type="VARCHAR(255)">
14+
<constraints nullable="false" unique="true" uniqueConstraintName="uk_user_username"/>
15+
</column>
16+
</createTable>
17+
18+
<createTable tableName="role">
19+
<column name="id" type="BIGINT" autoIncrement="true">
20+
<constraints primaryKey="true" primaryKeyName="pk_role" nullable="false"/>
21+
</column>
22+
<column name="authority" type="VARCHAR(255)">
23+
<constraints nullable="false" unique="true" uniqueConstraintName="uk_role_authority"/>
24+
</column>
25+
</createTable>
26+
27+
<createTable tableName="user_role">
28+
<column name="id_user_id" type="BIGINT">
29+
<constraints nullable="false"/>
30+
</column>
31+
<column name="id_role_id" type="BIGINT">
32+
<constraints nullable="false"/>
33+
</column>
34+
</createTable>
35+
36+
<addPrimaryKey tableName="user_role" constraintName="pk_user_role" columnNames="id_user_id, id_role_id"/>
37+
38+
<addForeignKeyConstraint
39+
baseTableName="user_role"
40+
baseColumnNames="id_user_id"
41+
constraintName="fk_user_role_user"
42+
referencedTableName="user"
43+
referencedColumnNames="id"
44+
onDelete="CASCADE"/>
45+
46+
<addForeignKeyConstraint
47+
baseTableName="user_role"
48+
baseColumnNames="id_role_id"
49+
constraintName="fk_user_role_role"
50+
referencedTableName="role"
51+
referencedColumnNames="id"
52+
onDelete="CASCADE"/>
53+
</changeSet>
54+
</databaseChangeLog>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<databaseChangeLog
3+
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
6+
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
7+
<include file="changelog/01-schema.xml" relativeToChangelogFile="true"/>
8+
</databaseChangeLog>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package example.micronaut;
2+
3+
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
4+
import org.junit.jupiter.api.Test;
5+
6+
import java.util.List;
7+
8+
import static org.junit.jupiter.api.Assertions.assertEquals;
9+
import static org.junit.jupiter.api.Assertions.assertFalse;
10+
import static org.junit.jupiter.api.Assertions.assertNotNull;
11+
12+
@MicronautTest(startApplication = false) // <1>
13+
class ManyToManyTest {
14+
15+
private static final String ROLE_USER = "ROLE_USER";
16+
private static final String ROLE_ADMIN = "ROLE_ADMIN";
17+
private static final String U_SERGIO = "sergio";
18+
private static final String U_TIM = "tim";
19+
20+
@Test
21+
void testManyToManyPersistence(RoleJdbcRepository roleRepo,
22+
UserJdbcRepository userRepo,
23+
UserRoleJdbcRepository userRoleRepo) {
24+
Role roleUser = roleRepo.save(ROLE_USER);
25+
Role roleAdmin = roleRepo.save(ROLE_ADMIN);
26+
27+
assertFalse(userRepo.findByUsername(U_SERGIO).isPresent());
28+
UserEntity sergio = userRepo.save(U_SERGIO);
29+
assertUser(userRepo.findByUsername(U_SERGIO).orElse(null), U_SERGIO, null);
30+
userRoleRepo.save(new UserRole(sergio, roleUser));
31+
userRoleRepo.save(new UserRole(sergio, roleAdmin));
32+
assertUser(userRepo.findByUsername(U_SERGIO).orElse(null), U_SERGIO, List.of(ROLE_ADMIN, ROLE_USER));
33+
34+
UserEntity tim = userRepo.save(U_TIM);
35+
userRoleRepo.save(new UserRole(tim, roleUser));
36+
assertUser(userRepo.findByUsername(U_TIM).orElse(null), U_TIM, List.of(ROLE_USER));
37+
38+
userRoleRepo.delete(new UserRole(tim, roleUser));
39+
userRoleRepo.delete(new UserRole(sergio, roleUser));
40+
userRoleRepo.delete(new UserRole(sergio, roleAdmin));
41+
42+
userRepo.delete(sergio);
43+
userRepo.delete(tim);
44+
45+
roleRepo.deleteByAuthority(ROLE_ADMIN);
46+
roleRepo.deleteByAuthority(ROLE_USER);
47+
}
48+
49+
void assertUser(User user, String expectedUsername, List<String> expectedAuthorities) {
50+
assertNotNull(user);
51+
assertNotNull(user.id());
52+
assertEquals(expectedUsername, user.username());
53+
assertEquals(expectedAuthorities, user.authorities());
54+
}
55+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"publish": false
3+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2017-2025 original authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example.micronaut;
17+
18+
import io.micronaut.data.jdbc.annotation.JdbcRepository;
19+
import io.micronaut.data.model.query.builder.sql.Dialect;
20+
import io.micronaut.data.repository.CrudRepository;
21+
import java.util.Optional;
22+
23+
@JdbcRepository(dialect = Dialect.MYSQL) // <1>
24+
public interface RoleJdbcRepository extends CrudRepository<Role, Long> { // <2>
25+
Role save(String authority);
26+
27+
Optional<Role> findByAuthority(String authority);
28+
29+
void deleteByAuthority(String authority);
30+
}

0 commit comments

Comments
 (0)