Query#getResultList() 始终 returns 特定实体的 1 大小列表
Query#getResultList() always returns a 1 sized list for a specific entity
这是我的测试方法:
@RunWithApplicationComposer(mode = ExtensionMode.PER_ALL)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class MyTest{
@PersistenceContext
EntityManager entityManager;
@Resource
private UserTransaction userTransaction;
private DB db;
private void startDb() throws ManagedProcessException {
DBConfigurationBuilder config = DBConfigurationBuilder.newBuilder();
config.setPort(0); // random port
db = DB.newEmbeddedDB(config.build());
db.start();
db.createDB("testdb", "root", "root");
db.source("schemaonly.sql", "root", "root", "testdb");
}
//resources.xml equivalent
@Configuration
public Properties config() throws ManagedProcessException {
startDb();
Properties p = new Properties();
p.put("db", "new://Resource?type=DataSource");
p.put("db.JdbcDriver", "com.mysql.cj.jdbc.Driver");
p.put("db.JdbcUrl", "jdbc:mysql://localhost:"+db.getConfiguration().getPort()+"/testdb?user=root&password=root");
return p;
}
//persistence.xml equivalent
@Module
public PersistenceUnit setupPU(){
PersistenceUnit pu = new PersistenceUnit("testPU");
pu.setProvider(HibernatePersistenceProvider.class);
pu.setJtaDataSource("db");
pu.setProperty("tomee.jpa.factory.lazy", "true");
pu.setProperty("hibernate.format_sql" ,"true");
pu.setProperty("hibernate.use_sql_comments" ,"true");
pu.setProperty("hibernate.dialect" ,"org.hibernate.dialect.MySQL8Dialect");
pu.setProperty("hibernate.physical_naming_strategy",
"org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy");
return pu;
}
@Test
void fetchPost() throws Exception{
userTransaction.begin();
User user = new User();
user.setUsername("username");
user.setAdmin(false);
user.setEmail("email");
user.setPassword("password".getBytes(StandardCharsets.UTF_8));
user.setSalt("salt".getBytes(StandardCharsets.UTF_8));
entityManager.persist(user);
Section section = new Section();
section.setName("section");
entityManager.persist(section);
IntStream.range(1, 10).mapToObj(n -> {
Post post = new Post();
post.setContent("content" + n);
post.setTitle("title" + n);
post.setType(Post.Type.TEXT);
post.setAuthor(user);
post.setSection(section);
return post;
}).forEach(entityManager::persist);
List<Post> from_post = entityManager.createQuery("select p from Post p", Post.class).getResultList();
Assertions.assertEquals(9,from_post.size());
userTransaction.commit();
}
}
我正在使用 MariaDB4j 以便为我的测试获取一个临时的嵌入式数据库。无需在测试之间清除数据库,因为我在此 class(用于演示目的)中只进行一项测试。
我还在使用 OpenEJB 的应用程序编辑器,以便在我的测试中 运行 Java EE 容器。
测试从一个空架构开始,该架构填充了 1 个用户、1 个部分和 9 个帖子。测试检查检索到的帖子是否真的是 9,否则测试失败。
问题是我得到以下结果:
org.opentest4j.AssertionFailedError:
Expected :9
Actual :1
这是我到目前为止尝试过的:
有 2 个独立的事务:一个持续存在,一个检索。我得到相同的结果
断言 select count(p) from Post p
的结果值为 9:测试通过。
在 persist
操作后立即调用本机查询(在事务内部和外部都尝试过):
entityManager.unwrap(Session.class).doWork(connection -> {
ResultSet resultSet = connection.createStatement().executeQuery("SELECT * From post");
while(resultSet.next()){
System.out.println(resultSet.getString("content"));
}
} );
我得到content1 content2 content3 ...
正在获取另一个实体:
@Test
void fetchUsers() throws Exception {
userTransaction.begin();
IntStream.range(1,10).mapToObj(n -> {
User user = new User();
user.setUsername("username" + n);
user.setAdmin(false);
user.setEmail("email" + n);
user.setPassword("password".getBytes(StandardCharsets.UTF_8));
user.setSalt("salt".getBytes(StandardCharsets.UTF_8));
return user;
}).forEach(entityManager::persist);
userTransaction.commit();
userTransaction.begin();
Assertions.assertEquals(9,entityManager.createQuery("from User",User.class).getResultList().size());
userTransaction.commit();
}
测试莫名其妙地通过了。我确保在任何测试之前都有一个干净的数据库。
在持久化操作后立即调用 flush()
和 clear()
。没有任何变化
我执行em.createQuery("select p from Post p", Post.class).getResultList()
。它打印 9 个标题
我就是无法理解它。可能是什么原因(可能还有解决方案)?
Post
@Entity
@DynamicUpdate
public class Post implements Serializable {
public enum Type {TEXT, IMG}
@Getter @Setter
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
protected Integer id;
@Setter @Getter
@Column(length = 255, nullable = false)
protected String title;
@Getter @Setter
@Column(columnDefinition = "TEXT", nullable = false)
protected String content;
@Setter @Getter
@Column(nullable = false) @Enumerated(EnumType.STRING)
protected Type type;
@Getter
@Column(nullable = false, updatable = false, insertable = false)
protected Instant creationDate; //generato da sql
@Getter
@Column(name = "votes", nullable = false, insertable = false, updatable = false)
protected Integer votesCount;
@Getter
@Formula("(select count(id) from Comment c where c.post_id = id group by c.post_id)") //native sql ew
protected Integer commentCount;
@Getter @Setter
@ManyToOne(fetch = FetchType.LAZY, optional = false)
protected Section section;
@Getter @Setter
@ManyToOne(fetch = FetchType.LAZY, optional = false)
protected User author;
@OneToMany(mappedBy="post")
@MapKeyJoinColumn(name="user_id", updatable = false, insertable = false)
protected Map<User, PostVote> votes = new HashMap<>();
public PostVote getVote(User user){
return votes.get(user);
}
public Post(){}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Post)) return false;
Post post = (Post) o;
return id != null && id.equals(post.id);
}
@Override
public int hashCode() {
return getClass().hashCode();
}
}
用户
@Entity
public class User implements Serializable {
@Getter @Setter
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
protected Integer id;
@Getter @Setter
@NaturalId(mutable = false) @Column(length = 30, unique = true, nullable = false)
protected String username;
@Getter @Setter
@Column(length = 16, nullable = false)
protected byte[] password;
@Getter @Setter
@Column(length = 16, nullable = false)
protected byte[] salt;
@Getter @Setter
@Column(length = 255, unique = true, nullable = false)
protected String email;
@Getter @Setter
@Column(length = 255)
protected String description;
@Getter @Setter
@Column(length = 4096)
protected String picture;
@Getter
@Column(insertable = false, updatable = false, nullable = false)
protected Instant creationDate;
@Getter @Setter
@Column(nullable = false)
protected Boolean admin;
@OneToMany(mappedBy = "user")
@OrderBy("endTime desc")
protected List<Ban> bans = new ArrayList<>();
public List<Ban> getBans(){
return Collections.unmodifiableList(bans);
}
public User(){}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o;
return id.equals(user.id);
}
@Override
public int hashCode() {
return getClass().hashCode();
}
}
部分
@Entity
public class Section implements Serializable {
@Getter @Setter
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
protected Integer id;
@Getter @Setter
@Column(length = 255)
protected String description;
@Getter @Setter
@Column(length = 50, nullable = false, unique = true) @NaturalId(mutable = true)
protected String name;
@Getter @Setter
@Column(length = 4096)
protected String picture;
@Getter @Setter
@Column(length = 4096)
protected String banner;
@OneToMany(mappedBy="section")
@MapKeyJoinColumn(name="user_id", updatable = false, insertable = false)
@LazyCollection(LazyCollectionOption.EXTRA)
protected Map<User, Follow> follows;
public Follow getFollow(User user){
return follows.get(user);
}
public int getFollowCount(){
return follows.size();
}
public Section(){}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Section)) return false;
Section section = (Section) o;
return id.equals(section.id);
}
@Override
public int hashCode() {
return getClass().hashCode();
}
}
环境
- TomEE 8.0.8
- 休眠 5.6.5.Final
- MariaDB4j 2.5.3
问题出在 Post#commentCount
字段中。它是通过带有此本机子查询的 @Formula
注释错误计算的:
SELECT count(id) FROM Comment c WHERE c.post_id = id group by c.post_id
其中 count(id)
等同于 count(post.id)
。生成的查询如下:
select
post0_.id as id1_4_,
post0_.author_id as author_i7_4_,
post0_.content as content2_4_,
post0_.creation_date as creation3_4_,
post0_.section_id as section_8_4_,
post0_.title as title4_4_,
post0_.type as type5_4_,
post0_.votes as votes6_4_,
(select
count(post0_.id)
from
Comment c
where
c.post_id = post0_.id
group by
c.post_id) as formula1_
from
post post0_
我只需要将 count(id)
替换为 count(c.id)
这是我的测试方法:
@RunWithApplicationComposer(mode = ExtensionMode.PER_ALL)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class MyTest{
@PersistenceContext
EntityManager entityManager;
@Resource
private UserTransaction userTransaction;
private DB db;
private void startDb() throws ManagedProcessException {
DBConfigurationBuilder config = DBConfigurationBuilder.newBuilder();
config.setPort(0); // random port
db = DB.newEmbeddedDB(config.build());
db.start();
db.createDB("testdb", "root", "root");
db.source("schemaonly.sql", "root", "root", "testdb");
}
//resources.xml equivalent
@Configuration
public Properties config() throws ManagedProcessException {
startDb();
Properties p = new Properties();
p.put("db", "new://Resource?type=DataSource");
p.put("db.JdbcDriver", "com.mysql.cj.jdbc.Driver");
p.put("db.JdbcUrl", "jdbc:mysql://localhost:"+db.getConfiguration().getPort()+"/testdb?user=root&password=root");
return p;
}
//persistence.xml equivalent
@Module
public PersistenceUnit setupPU(){
PersistenceUnit pu = new PersistenceUnit("testPU");
pu.setProvider(HibernatePersistenceProvider.class);
pu.setJtaDataSource("db");
pu.setProperty("tomee.jpa.factory.lazy", "true");
pu.setProperty("hibernate.format_sql" ,"true");
pu.setProperty("hibernate.use_sql_comments" ,"true");
pu.setProperty("hibernate.dialect" ,"org.hibernate.dialect.MySQL8Dialect");
pu.setProperty("hibernate.physical_naming_strategy",
"org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy");
return pu;
}
@Test
void fetchPost() throws Exception{
userTransaction.begin();
User user = new User();
user.setUsername("username");
user.setAdmin(false);
user.setEmail("email");
user.setPassword("password".getBytes(StandardCharsets.UTF_8));
user.setSalt("salt".getBytes(StandardCharsets.UTF_8));
entityManager.persist(user);
Section section = new Section();
section.setName("section");
entityManager.persist(section);
IntStream.range(1, 10).mapToObj(n -> {
Post post = new Post();
post.setContent("content" + n);
post.setTitle("title" + n);
post.setType(Post.Type.TEXT);
post.setAuthor(user);
post.setSection(section);
return post;
}).forEach(entityManager::persist);
List<Post> from_post = entityManager.createQuery("select p from Post p", Post.class).getResultList();
Assertions.assertEquals(9,from_post.size());
userTransaction.commit();
}
}
我正在使用 MariaDB4j 以便为我的测试获取一个临时的嵌入式数据库。无需在测试之间清除数据库,因为我在此 class(用于演示目的)中只进行一项测试。
我还在使用 OpenEJB 的应用程序编辑器,以便在我的测试中 运行 Java EE 容器。
测试从一个空架构开始,该架构填充了 1 个用户、1 个部分和 9 个帖子。测试检查检索到的帖子是否真的是 9,否则测试失败。
问题是我得到以下结果:
org.opentest4j.AssertionFailedError:
Expected :9
Actual :1
这是我到目前为止尝试过的:
有 2 个独立的事务:一个持续存在,一个检索。我得到相同的结果
断言
select count(p) from Post p
的结果值为 9:测试通过。在
persist
操作后立即调用本机查询(在事务内部和外部都尝试过):entityManager.unwrap(Session.class).doWork(connection -> { ResultSet resultSet = connection.createStatement().executeQuery("SELECT * From post"); while(resultSet.next()){ System.out.println(resultSet.getString("content")); } } );
我得到
content1 content2 content3 ...
正在获取另一个实体:
@Test void fetchUsers() throws Exception { userTransaction.begin(); IntStream.range(1,10).mapToObj(n -> { User user = new User(); user.setUsername("username" + n); user.setAdmin(false); user.setEmail("email" + n); user.setPassword("password".getBytes(StandardCharsets.UTF_8)); user.setSalt("salt".getBytes(StandardCharsets.UTF_8)); return user; }).forEach(entityManager::persist); userTransaction.commit(); userTransaction.begin(); Assertions.assertEquals(9,entityManager.createQuery("from User",User.class).getResultList().size()); userTransaction.commit(); }
测试莫名其妙地通过了。我确保在任何测试之前都有一个干净的数据库。
在持久化操作后立即调用
flush()
和clear()
。没有任何变化我执行
em.createQuery("select p from Post p", Post.class).getResultList()
。它打印 9 个标题
我就是无法理解它。可能是什么原因(可能还有解决方案)?
Post
@Entity
@DynamicUpdate
public class Post implements Serializable {
public enum Type {TEXT, IMG}
@Getter @Setter
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
protected Integer id;
@Setter @Getter
@Column(length = 255, nullable = false)
protected String title;
@Getter @Setter
@Column(columnDefinition = "TEXT", nullable = false)
protected String content;
@Setter @Getter
@Column(nullable = false) @Enumerated(EnumType.STRING)
protected Type type;
@Getter
@Column(nullable = false, updatable = false, insertable = false)
protected Instant creationDate; //generato da sql
@Getter
@Column(name = "votes", nullable = false, insertable = false, updatable = false)
protected Integer votesCount;
@Getter
@Formula("(select count(id) from Comment c where c.post_id = id group by c.post_id)") //native sql ew
protected Integer commentCount;
@Getter @Setter
@ManyToOne(fetch = FetchType.LAZY, optional = false)
protected Section section;
@Getter @Setter
@ManyToOne(fetch = FetchType.LAZY, optional = false)
protected User author;
@OneToMany(mappedBy="post")
@MapKeyJoinColumn(name="user_id", updatable = false, insertable = false)
protected Map<User, PostVote> votes = new HashMap<>();
public PostVote getVote(User user){
return votes.get(user);
}
public Post(){}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Post)) return false;
Post post = (Post) o;
return id != null && id.equals(post.id);
}
@Override
public int hashCode() {
return getClass().hashCode();
}
}
用户
@Entity
public class User implements Serializable {
@Getter @Setter
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
protected Integer id;
@Getter @Setter
@NaturalId(mutable = false) @Column(length = 30, unique = true, nullable = false)
protected String username;
@Getter @Setter
@Column(length = 16, nullable = false)
protected byte[] password;
@Getter @Setter
@Column(length = 16, nullable = false)
protected byte[] salt;
@Getter @Setter
@Column(length = 255, unique = true, nullable = false)
protected String email;
@Getter @Setter
@Column(length = 255)
protected String description;
@Getter @Setter
@Column(length = 4096)
protected String picture;
@Getter
@Column(insertable = false, updatable = false, nullable = false)
protected Instant creationDate;
@Getter @Setter
@Column(nullable = false)
protected Boolean admin;
@OneToMany(mappedBy = "user")
@OrderBy("endTime desc")
protected List<Ban> bans = new ArrayList<>();
public List<Ban> getBans(){
return Collections.unmodifiableList(bans);
}
public User(){}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o;
return id.equals(user.id);
}
@Override
public int hashCode() {
return getClass().hashCode();
}
}
部分
@Entity
public class Section implements Serializable {
@Getter @Setter
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
protected Integer id;
@Getter @Setter
@Column(length = 255)
protected String description;
@Getter @Setter
@Column(length = 50, nullable = false, unique = true) @NaturalId(mutable = true)
protected String name;
@Getter @Setter
@Column(length = 4096)
protected String picture;
@Getter @Setter
@Column(length = 4096)
protected String banner;
@OneToMany(mappedBy="section")
@MapKeyJoinColumn(name="user_id", updatable = false, insertable = false)
@LazyCollection(LazyCollectionOption.EXTRA)
protected Map<User, Follow> follows;
public Follow getFollow(User user){
return follows.get(user);
}
public int getFollowCount(){
return follows.size();
}
public Section(){}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Section)) return false;
Section section = (Section) o;
return id.equals(section.id);
}
@Override
public int hashCode() {
return getClass().hashCode();
}
}
环境
- TomEE 8.0.8
- 休眠 5.6.5.Final
- MariaDB4j 2.5.3
问题出在 Post#commentCount
字段中。它是通过带有此本机子查询的 @Formula
注释错误计算的:
SELECT count(id) FROM Comment c WHERE c.post_id = id group by c.post_id
其中 count(id)
等同于 count(post.id)
。生成的查询如下:
select
post0_.id as id1_4_,
post0_.author_id as author_i7_4_,
post0_.content as content2_4_,
post0_.creation_date as creation3_4_,
post0_.section_id as section_8_4_,
post0_.title as title4_4_,
post0_.type as type5_4_,
post0_.votes as votes6_4_,
(select
count(post0_.id)
from
Comment c
where
c.post_id = post0_.id
group by
c.post_id) as formula1_
from
post post0_
我只需要将 count(id)
替换为 count(c.id)