美文网首页
Spring boot JPA:Unknown entity 解

Spring boot JPA:Unknown entity 解

作者: JasonGofen | 来源:发表于2019-06-10 14:46 被阅读0次

    前言

    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文件
    我们发现两种方式编译后产出的文件数量不一致,采用匿名内部类的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类在编译后,会多出一个继承自UserTest$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注解,但是继承Usert1类并没有添加@Entity注解,故而无法被JPA识别。

    当然大家可能还有一些疑问:
    ① 给t1添加@Entity注解不就行了么?
    当然可以,我们采用这个{{ set(); }}方式来写的目的,就是为了代码更简单一些,但如果采用问题中描述的那样来做,其实也就没多大必要了。
    ② 难道我们不可以继承父类的注解吗?
    目前@Entity不可以继承。原因是在Spring boot的public @interface Entity{...}注解类中并没有@Inherited,故不可继承。至于为何要这样还未深入研究。

    相关文章

      网友评论

          本文标题:Spring boot JPA:Unknown entity 解

          本文链接:https://www.haomeiwen.com/subject/ibrcxctx.html