前言
JPA Unknown entity 其实是一个简单的问题,可因为基础不牢导致该问题的解决变得很耗时,特在此记录以示警示。下面表述的内容可能有不当之处,如有幸被各位看到,还望批评指正,非常感谢!
问题
最近在学习shiro权限管理的时使用JPA来做单表的增删改查,发现出现
java.lang.IllegalArgumentException: Unknown entity:***.***.***Entity
,排查了很久也没发现问题,最后因为是类的实例化问题。
答案
我的遇到的问题和解决方法如下。
/**
* 用户注册
*
* @param account
* @return
*/
@GetMapping("/register")
public RestfulModel ajaxRegister(@Valid Account account, HttpServletRequest request) throws Exception {
if (!Optional.ofNullable(account).map(Account::getUsername).isPresent()
|| !Optional.ofNullable(account).map(Account::getPassword).isPresent()) {
throw new Exception("账号或密码不能为空!");
}
// 生成盐
String salt = new SecureRandomNumberGenerator().nextBytes().toHex();
//将原始密码加盐(上面生成的盐),并且用md5算法加密2次,将最后结果存入库中
String password = new Md5Hash(account.getPassword(), account.getCredentialsSalt(), 2).toString();
accountService.save(new Account(){{
setSalt(salt);
setPassword(password);
setLastLoginIp(IpUtil.getIpAddr(request));
setLastLoginTime(new Date());
}});
return new RestfulModel() {{
setStatus("success");
}};
}
报错出现在第11行,当代码执行到11行开始报错,说unkonw entity
,其实改成下面这样就可以了。
/**
* 用户注册
*
* @param account
* @return
*/
1 @GetMapping("/register")
2 public RestfulModel ajaxRegister(@Valid Account account, HttpServletRequest request) throws Exception {
3 if (!Optional.ofNullable(account).map(Account::getUsername).isPresent()
4 || !Optional.ofNullable(account).map(Account::getPassword).isPresent()) {
5 throw new Exception("账号或密码不能为空!");
6 }
7 // 生成盐
8 String salt = new SecureRandomNumberGenerator().nextBytes().toHex();
9 //将原始密码加盐(上面生成的盐),并且用md5算法加密2次,将最后结果存入数据库中
10 String password = new Md5Hash(account.getPassword(), account.getCredentialsSalt(), 2).toString();
11 account.setSalt(salt);
12 account.setPassword(password);
13 account.setLastLoginIp(IpUtil.getIpAddr(request));
14 account.setLastLoginTime(new Date());
15 accountService.save(account);
16 return new RestfulModel() {{
17 setStatus("success");
18 }};
19 }
总结
如果你遇到的问题与我的不一样,请看:
① 请使用javax.persistence.Entity
为你的实体类添加@Entity
注解;
② 你可以尝试在YouProjectNameApplication.java
入口注解处添加强制扫描Entity注解@EntityScan( basePackages = {"your.packagename.xxxEntity"}
;
③ 请采用上述原始的set
方法,为你的Entity
属性赋值,不要使用匿名类的方式。
解析
- JPA在做
save
操作时,放入的实体对象必须有@Entity
注解,而采用匿名类的set
方式导致JPA不能正确识别实体,所以会出现Unknown entity
的报错。
那么问题来了:
为何采用上述的匿名内部类{{ set(); }}
方法不能达到预期效果?原始的set方法与匿名内部类中的set方法有何不同?
- 关于上述这个问题,我们有必要去了解一下Java中匿名内部类。这个问题纠结了很长时间,查看了很多博客又问了下公司的大牛最后又看了下java编程思想(第四版) / 第10章 内部类 / 10.6 匿名内部类才大概捋清楚为何JPA不能识别的问题。以下为个人理解,如有不当还请留言指正!
首先先定义个User实体类,以便后面使用:
public class User {
private String email;
private String something;
// 省略getter、setter及toString
...
}
1. 普通类与匿名内部类的编译对比
普通类的编译一般只会产出一个.class
文件:
public class Test {
public static void main(String[] args) {
User user = new User();
user.setEmail("gofen2010@163.com");
user.setSomething("Send email!");
System.out.println(user.toString());
}
}
编译后:
普通Test类编译后的.class文件
匿名内部类会产出两个以上的.class
文件:
// Test.java
public class Test {
public static void main(String[] args) {
User user = new User(){{
setEmail("gofen2010@163.com");
setSomething("Send email!");
}};
System.out.println(user.toString());
}
}
编译后:
我们发现两种方式编译后产出的文件数量不一致,采用匿名内部类的
Test.class
要比上面多一个。
2. 普通类与匿名内部类编译后的.class
内容对比
普通类.class内容:
// Test.class
public class Test {
public Test() {
}
public static void main(String[] args) {
User user = new User();
user.setEmail("gofen2010@163.com");
user.setSomething("Send email!");
System.out.println(user.toString());
}
}
匿名内部类的.class内容:
// Test.class
public class Test {
public Test() {
}
public static void main(String[] args) {
User user = new User() {
{
this.setEmail("gofen2010@163.com");
this.setSomething("Send email!");
}
};
System.out.println(user.toString());
}
}
// Test$1.class
final class Test$1 extends User {
Test$1() {
this.setEmail("gofen2010@163.com");
this.setSomething("Send email!");
}
}
带有匿名内部类的Test.java
类在编译后,会多出一个继承自User
的Test$1.class
,而如果你看完java编程思想(第四版) / 第10章 内部类 / 10.6 匿名内部类,你会发现带有匿名内部类的Test.java
类可以写成下面这样。
3. 改造带有匿名内部类的Test.java
类
我们可以这样写:
// Test.java
public class Test {
public static void main(String[] args) {
User user = new t1();
System.out.println(user.toString());
}
static class t1 extends User {
t1() {
this.setEmail("gofen2010@163.com");
this.setSomething("Send email");
}
}
}
我想看到这大家应该就明白了,为何采用{{ set(); }}
的方式就不能被JPA识别了。假设User.java
类添加了@Entity
注解,但是继承User
的t1
类并没有添加@Entity
注解,故而无法被JPA识别。
当然大家可能还有一些疑问:
① 给t1
添加@Entity
注解不就行了么?
当然可以,我们采用这个{{ set(); }}
方式来写的目的,就是为了代码更简单一些,但如果采用问题中描述的那样来做,其实也就没多大必要了。
② 难道我们不可以继承父类的注解吗?
目前@Entity
不可以继承。原因是在Spring boot的public @interface Entity{...}
注解类中并没有@Inherited
,故不可继承。至于为何要这样还未深入研究。
网友评论