SpringData JpaRepository not working when using @e

2019-06-22 01:13发布

I have a new SpringBoot app to expose some services related to entities that live in a jar (included as a dependency in the POM). For the data access I plan to use SpringData so I can use the great JpaRepository instead of writing the DAO manually.

The jar is visible from the code so everything compiles fine, but when Spring starts wiring the beans it throws the exception:

Caused by: java.lang.IllegalArgumentException: Not a managed type: class com.companyName.common.countrytype.Country
    at org.hibernate.jpa.internal.metamodel.MetamodelImpl.managedType(MetamodelImpl.java:210) ~[hibernate-entitymanager-5.0.11.Final.jar:5.0.11.Final]
    at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation.<init>(JpaMetamodelEntityInformation.java:70) ~[spring-data-jpa-1.10.3.RELEASE.jar:na]

I have tried adding @EnableJpaRepositories pointing to the package of the interface that extends JpaRepository but still without success.

HOWEVER, everything works great if I use a copy of the @Entity pasted in my new project. (instead of using the entity from the jar)

Do you see something I am missing to use an SpringData/JpaEntity with an @Entity declared in a jar? Have you ever done something similar?

This is my code:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.mycompany</groupId>
	<artifactId>country-service</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>country-service</name>
	<description>Spring Boot country-service</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.4.1.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-ws</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>com.mycompany</groupId>
			<artifactId>mycompany-core</artifactId>
			<version>1.1.20</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

Entry point:

package com.mycompany.admin.api;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@SpringBootApplication
@EnableJpaRepositories(basePackages={"com.mycompany.admin.api"}) //tried this but does not work
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Repository:

package com.mycompany.admin.api;

import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import com.mycompany.common.countrytype.Country;  //This is located in a jar included as dependency in the pom

public interface CountryRepository extends JpaRepository<Country,  Long> {

}

A simple Controller to test:

package com.mycompany.admin.api;

import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.mycompany.common.countrytype.Country;

@Controller
public class CountryController {

    @Autowired
    private CountryRepository repo;

    @RequestMapping("/")
    @ResponseBody
    public String greeting() {
        return "Hello";
    }


    @RequestMapping("/countries")
    @ResponseBody
    public String listCountry() {
        List<Country> countries;
        try {
            countries = repo.findAll();
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return e.getMessage();
        }
        if (countries.isEmpty()) {
            String errorMst = "no countries found";
            System.out.println(errorMst);
            return errorMst;
        } else {
            return "size:" + countries.size();
        }
    } 
}

The entity:

package com.mycompany.admin.api;

import static javax.persistence.GenerationType.IDENTITY;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

@Entity
@Table(name = "country", uniqueConstraints = { @UniqueConstraint(columnNames = "code"), @UniqueConstraint(columnNames = "name") })
public class Country {
    protected Long id;
    private String code;
    private String name;

    protected Country() {
    }

    public Country(Long id, String code, String name) {
        this.id = id;
        this.code = code;
        this.name = name;
    }

    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "id", unique = true, nullable = false)
    public Long getId() {
        return this.id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Column(name = "code", unique = true, nullable = false, length = 2)
    public String getCode() {
        return this.code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    @Column(name = "name", unique = true, nullable = false, length = 100)
    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        if (!super.equals(o)) {
            return false;
        }

        Country country = (Country) o;

        if (!code.equals(country.code)) {
            return false;
        }
        if (!name.equals(country.name)) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int result = super.hashCode();
        result = 31 * result + code.hashCode();
        result = 31 * result + name.hashCode();
        return result;
    }

}

The weird thing is that if I remove the lines "import com.mycompany.common.countrytype.Country;" in my Repository and Controller classes so the code uses a cloned version of it that is stored in the new boot project this works OK. So the issue is only happening when using the Country entity from the jar.

I really appreciate any hint or advise you could give me.

Thank you in advance!

4条回答
Animai°情兽
2楼-- · 2019-06-22 01:27

In my project we experienced a similar problem. When running with mvn spring-boot:start the @EntityScan works fine and can find all entities including those in libraries. When we run the springboot application using java -jar the application cannot find entities that are referenced via a maven dependency. It seems the classloading is different when the springboot application is run as a jar.

You can instruct spring-boot to 'unpack' the JAR dependency that contains the entities that can not be found. You can add the following section to your pom.xml:

<project>
  ...

  <build>
      <plugins>
          <plugin>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-maven-plugin</artifactId>
              <configuration>
                  <requiresUnpack>
                      <dependency>
                          <groupId>com.yourcompany.package</groupId>
                          <artifactId>some-jar</artifactId>
                      </dependency>
                  </requiresUnpack>
              </configuration>
          </plugin>
      </plugins>
  </build>

</project>
查看更多
Lonely孤独者°
3楼-- · 2019-06-22 01:28

My project had a similar problem with a Spring Boot application. I had the @Entity class in the common jar at a package such as com.foo.jpa, then an application jar that depended on the common jar. The application jar had the main @SpringBootApplication class at a package such as com.foo.bar.appname. @EntityScan, @ComponentScan, and @EnableJpaRepositories did not detect the @Entity class and gave the "Not a managed type" error regardless of what package names I provided to the annotations.

I finally fixed it by renaming the package names. I put the @SpringBootApplicationapplication class at com.foo.bar and the @Entity class in the common jar at com.foo.bar.whatever. As long as the @Entity class in the common jar was at the same package or a subpackage of the @SpringBootApplication class, it was autodetected, regardless of which jar it was in.

查看更多
Anthone
4楼-- · 2019-06-22 01:29

It works for me when adding @Bean with package scan

@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
    LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
    HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
   factory.setDataSource(dataSource());
    factory.setJpaVendorAdapter(vendorAdapter);
    factory.setPackagesToScan("com.xxx.entity"); // This one
    factory.afterPropertiesSet();
    factory.setLoadTimeWeaver(new InstrumentationLoadTimeWeaver());
    return factory;
}
查看更多
戒情不戒烟
5楼-- · 2019-06-22 01:40

Use @EntityScan("your.lib.package.with.entities")

查看更多
登录 后发表回答