Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SpringData JpaRepository not working when using @entity from a jar

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!

like image 298
munilvc Avatar asked Oct 20 '16 02:10

munilvc


2 Answers

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

like image 154
kjsebastian Avatar answered Nov 14 '22 23:11

kjsebastian


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.

like image 24
ndjensen Avatar answered Nov 15 '22 01:11

ndjensen