我有一个名为user的实体,如下所示:
@Entity
@JsonSerialize(using = UserSerializer.class)
@Table(uniqueConstraints={@UniqueConstraint(columnNames = {"username"})})
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, updatable = false)
private String username;
private String password;
@OneToOne(cascade={CascadeType.ALL})
@JoinColumn(name = "person_id")
private Person person;
@OneToOne(cascade={CascadeType.PERSIST})
@JoinColumn(name = "maintainer_id")
private Maintainer maintainer;
@ManyToMany(cascade={CascadeType.PERSIST, CascadeType.MERGE}, fetch =
FetchType.EAGER)
@JoinTable(name = "user_role",
joinColumns = @JoinColumn(name = "user_id",
nullable = false, updatable = false),
inverseJoinColumns = @JoinColumn(name = "role_id",
nullable = false, updatable = false))
private Set<Role> roles;
public User() {
}
public User(String username, String password, Person person, Maintainer maintainer, Set<Role> roles) {
this.username = username;
this.password = password;
this.person = person;
this.maintainer = maintainer;
this.roles = roles;
}
// builder inner class omitted
// getters and setters omitted
// equals, hashCode and toString omitted
}
我还有一个用户摘要类,旨在将其返回以进行分页和排序:
public class UserRow {
private Long id;
private String username;
private String firstName;
private String lastName;
private Set<Role> roles;
private String role;
private Long maintainerId;
private String maintainerName;
public static UserRow of(Long id, String username, String firstName, String lastName, Set<Role> roles, Long maintainerId, String maintainerName) {
UserRow userRow = new UserRow();
userRow.id = id;
userRow.username = username;
userRow.firstName = firstName;
userRow.lastName = lastName;
userRow.roles = roles;
userRow.maintainerId = maintainerId;
userRow.maintainerName = maintainerName;
return userRow;
}
// getters, setters, equals, hashCode and toString omitted
}
这与所有其他实体一起似乎在h2中很好地初始化了,从控制台中我看到了类似的东西:
create table user (
id bigint generated by default as identity,
password varchar(255),
username varchar(255),
maintainer_id bigint,
person_id bigint,
primary key (id)
)
和
create table user_role (
user_id bigint not null,
role_id bigint not null,
primary key (user_id, role_id)
)
甚至:
alter table user
add constraint UK_sb8bbouer5wak8vyiiy4pf2bx unique (username)
现在,我已经设置了控制器,服务和存储库...
控制器:
@Secured("ROLE_ADMIN")
@GetMapping(value = {"/users", "/users/maintainers/{mid}"}, produces = "application/json")
@ResponseStatus(value = HttpStatus.OK)
Response<List<User>> getPagedUsers(
@PathVariable("mid") Optional<Long> maintainerId,
@PageableDefault(page = DEFAULT_PAGE_NUMBER, size = DEFAULT_PAGE_SIZE)
@SortDefault.SortDefaults({
@SortDefault(sort = "username", direction = Sort.Direction.ASC),
@SortDefault(sort = "p.lastName", direction = Sort.Direction.ASC)
}) Pageable pageable) {
Page<UserRow> users;
if (maintainerId.isPresent()) {
users = userService.findSortedSummaryByMaintainer(maintainerId.get(), pageable);
} else {
users = userService.findSortedSummary(pageable);
}
return Response.of(users);
}
服务: 公共接口UserService扩展了UserDetailsService {
Page<UserRow> findSortedSummary(Pageable pageable);
@Service
class UserServiceImpl implements UserService {
private static final Logger LOG = LoggerFactory.getLogger(UserService.class);
@Autowired
BCryptPasswordEncoder bCryptPasswordEncoder;
@Autowired
UserRepository userRepository;
@Autowired
RoleRepository roleRepository;
public Page<UserRow> findSortedSummary(Pageable pageable) {
Page<UserRow> userPage = userRepository.findSortedSummary(pageable.next());
userPage.forEach(us -> us.setRole(findMostPrivilegedRole(us.getRoles())));
return userPage;
}
}
}
和存储库: 公共接口UserRepository扩展了JpaRepository {
@Query(value = "select u.username, p.firstName, p.lastName, u.roles, m.id, m.name "
+ "from User u "
+ "inner join u.person p "
+ "inner join u.maintainer m",
countQuery = "select count(*) "
+ "from User u "
+ "inner join u.person p "
+ "inner join u.maintainer m",
nativeQuery = true)
Page<UserRow> findSortedSummary(Pageable pageable);
}
最后,我正在用以下方法测试所有这些:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("embedded")
@AutoConfigureMockMvc
public class UserListControllerFunctionalTest {
@Autowired
private WebApplicationContext context;
@Autowired
private MockMvc mvc;
@Autowired
UserService userService;
List<User> franks;
@Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
franks = ModelFixtures.createFrankAndCohorts();
franks = userService.createAll(franks);
}
@Test
public void getListOfUsersWithRootSuccess() throws Exception {
mvc.perform(get("/api/users").header(AUTHORIZATION_HEADER, "Bearer " + ModelFixtures.ROOT_JWT_TOKEN))
.andDo(print())
.andExpect(status().isOk());
}
@After
public void tearDown() {
userService.deleteAll(franks);
}
}
所有的spring security东西都经过了广泛的测试并可以正常工作。
测试调用控制器,服务和存储库时,我在存储库中遇到以下故障:
由以下原因引起:org.h2.jdbc.JdbcSQLException:找不到架构“ U”;的SQL 语句:选择u.username,p.firstName,p.lastName,u.roles,m.id, 用户的m。名称u内部联接u.person p内部联接u.maintainer m 按u.username asc,p.lastName asc限制排序?抵消? [90079-197] 在 org.h2.message.DbException.getJdbcSQLException(DbException.java:357) 在org.h2.message.DbException.get(DbException.java:179)处 org.h2.message.DbException.get(DbException.java:155)
据我到目前为止发现,别名表如“ User”和字母如“ u”都很好,h2(和MySQL)中用户的内部表是“ users”,因此“ User”未保留。所以我看不出问题出在哪里。有人可以帮忙吗?
相关依赖项:
| +--- org.hibernate:hibernate-core:5.2.17.Final
| | +--- org.jboss.logging:jboss-logging:3.3.1.Final -> 3.3.2.Final
| | +--- org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final
| | +--- org.javassist:javassist:3.22.0-GA
| | +--- antlr:antlr:2.7.7
| | +--- org.jboss:jandex:2.0.3.Final
| | +--- com.fasterxml:classmate:1.3.0 -> 1.3.4
| | +--- dom4j:dom4j:1.6.1
| | \--- org.hibernate.common:hibernate-commons-annotations:5.0.1.Final
| | \--- org.jboss.logging:jboss-logging:3.3.0.Final -> 3.3.2.Final
| +--- javax.transaction:javax.transaction-api:1.2
| +--- org.springframework.data:spring-data-jpa:2.0.7.RELEASE
| | +--- org.springframework.data:spring-data-commons:2.0.7.RELEASE
| | | +--- org.springframework:spring-core:5.0.6.RELEASE (*)
| | | +--- org.springframework:spring-beans:5.0.6.RELEASE (*)
| | | \--- org.slf4j:slf4j-api:1.7.25
| | +--- org.springframework:spring-orm:5.0.6.RELEASE
| | | +--- org.springframework:spring-beans:5.0.6.RELEASE (*)
| | | +--- org.springframework:spring-core:5.0.6.RELEASE (*)
| | | +--- org.springframework:spring-jdbc:5.0.6.RELEASE (*)
| | | \--- org.springframework:spring-tx:5.0.6.RELEASE (*)
| | +--- org.springframework:spring-context:5.0.6.RELEASE (*)
| | +--- org.springframework:spring-aop:5.0.6.RELEASE (*)
| | +--- org.springframework:spring-tx:5.0.6.RELEASE (*)
| | +--- org.springframework:spring-beans:5.0.6.RELEASE (*)
| | +--- org.springframework:spring-core:5.0.6.RELEASE (*)
| | \--- org.slf4j:slf4j-api:1.7.25
编辑-删除了nativeQuery = true
部分,解决了“找不到架构”问题-本机查询必须意味着按字面意义对待所有元素。
然而问题却变成了:
Caused by:
org.h2.jdbc.JdbcSQLException: Syntax error in SQL statement "
SELECT
USER0_.USERNAME AS COL_0_0_,
PERSON1_.FIRST_NAME AS COL_1_0_,
PERSON1_.LAST_NAME AS COL_2_0_,
.[*] AS COL_3_0_,
MAINTAINER2_.ID AS COL_4_0_,
MAINTAINER2_.NAME AS COL_5_0_,
ROLE4_.ID AS ID1_17_,
ROLE4_.DESCRIPTION AS DESCRIPT2_17_,
ROLE4_.ROLE_NAME AS ROLE_NAM3_17_
FROM USER USER0_
INNER JOIN PERSON PERSON1_ ON USER0_.PERSON_ID=PERSON1_.ID
INNER JOIN MAINTAINER MAINTAINER2_ ON USER0_.MAINTAINER_ID=MAINTAINER2_.ID
INNER JOIN USER_ROLE ROLES3_ ON USER0_.ID=ROLES3_.USER_ID
INNER JOIN ROLE ROLE4_ ON ROLES3_.ROLE_ID=ROLE4_.ID
ORDER BY USER0_.USERNAME ASC, PERSON1_.LAST_NAME ASC LIMIT ? OFFSET ? ";
expected "*, NOT, EXISTS, INTERSECTS, SELECT, FROM, WITH";
不确定在这里预期的“预期”事情在哪里...
答案 0 :(得分:0)
好,我已经解决了。
一切都在查询构造中。因此,正确执行jpql的方法是:
@Query(value = "select new au.com.avmaint.api.access.model.UserRow(u.id, u.username, p.firstName, p.lastName, m.id, m.name) "
+ "from User u "
+ "inner join u.person p "
+ "inner join u.maintainer m",
countQuery = "select count(*) "
+ "from User u "
+ "inner join u.person p "
+ "inner join u.maintainer m")
Page<UserRow> findSortedSummary(Pageable pageable);
如果打算返回非映射类(UserRow),则需要使用构造函数表示法。您也不能在SELECT子句中返回集合(user.roles),因此我不得不在另一个查询中获得角色。这很容易在服务层中完成(请参见forEach
块的第一行):
public Page<UserRow> findSortedSummary(Pageable pageable) {
Page<UserRow> userPage = userRepository.findSortedSummary(pageable.next());
userPage.forEach(row -> {
User user = userRepository.findById(row.getId()).orElseThrow(() -> new UsernameNotFoundException("User not found: " + row.getUsername()));
row.setRoles(user.getRoles());
row.setRole(findMostPrivilegedRole(user.getRoles()));
});
return userPage;
}
在这里,我使用了标准的Repository findById调用,以按ID返回单个用户并以这种方式获取角色。在单个查询中获得角色当然会很好,但是JPQL只是不具备该功能。
最后我可能会说,除了最琐碎的域示例(基本上是被博客的示例)外,对其他任何内容的分页和排序实际上都是非常棘手和复杂的。一旦我真的把这些东西整理好了,我就会在这个博客上写一些实际上有用的东西。