Я изучаю Spring и Hibernate по книге, и есть дополнительное упражнение для создания отношения «многие ко многим» с помощью Hibernate. К сожалению, автор не разместил необязательный пример на GitHub, и я теряюсь в отладке чего-то нового.

Вот части кода, он работает как отношение «один ко многим» между таблицами «Владелец» и «Автомобиль», но не работает как «многие ко многим» из-за указанной выше ошибки в заголовке. Здесь задействовано несколько файлов, но я опубликовал изображение файловой структуры Eclipse.

Cardatabase / pom файл:

<?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>

<!-- Maven uses this POM file to determine dependencies -->

<groupId>com.packt</groupId>
<artifactId>cardatabase</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>cardatabase</name>
<description>Demo project for Spring Boot</description>


<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version> 2.0.4.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-data-jpa</artifactId>
        <version> 2.0.4.RELEASE</version><!--$NO-MVN-MAN-VER$-->
    </dependency>       

    <!-- This is the dependency for the MariaDB program -->
    <dependency>
         <groupId>org.mariadb.jdbc</groupId>
         <artifactId>mariadb-java-client</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

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

</project>

Application.properties:

logging.level.root = INFO
spring.jpa.show-sql=true

spring.datasource.url=jdbc:mariadb://localhost:3306/cardb
spring.datasource.username=root
spring.datasource.password=nesatis
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver

spring.jpa.generate-ddl=true
spring.jpa.generate.ddl-auto=create-drop

CardatabaseApplication.java

package com.packt.cardatabase;

import org.springframework.beans.factory.annotation.Autowired;//This enables dependency injection
//These next four lines are for the commandlinerunner which allows code to run before the application has fully started.
 import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import com.packt.cardatabase.domain.Car;
import com.packt.cardatabase.domain.CarRepository;
import com.packt.cardatabase.domain.Owner;
import com.packt.cardatabase.domain.OwnerRepository;


@SpringBootApplication //Enables spring boot automatic configuration
public class CardatabaseApplication 
{
@Autowired //This is used to enable dependency injection
private CarRepository repository;

@Autowired //Inject ownerrepository into the main class
private OwnerRepository orepository;

public static void main(String[] args) 
{
    //After adding this comment the application is restarted.
    SpringApplication.run(CardatabaseApplication.class, args);
}

@Bean
CommandLineRunner runner() {
    return args ->{
        Owner owner1 = new Owner("John", "Johnson");
        Owner owner2 = new Owner("Mary", "Johnson");
        orepository.save(owner1);
        orepository.save(owner2);

        // Enter Car data here. This data must fit the Car constructor String X4 int X2
        // Methods such as save are a part of the CRUD 
        repository.save(new Car("Ford", "Mustang", "Red" , "ADF-1121", 2017, 59000, owner1));
        repository.save(new Car("Nissan", "Leaf", "White", "SSJ-3002", 2014, 29000, owner2));
        repository.save(new Car("Toyota", "Prius", "Silver", "KKO-0212", 2018, 39000, owner2));
        repository.save(new Car("Honda", "Accord", "White", "AH46505", 2014, 25000, owner1));

    };
}
}

Car.java

package com.packt.cardatabase.domain;


import java.util.Set; //Imported to be able to use the Set method.

//Note this relies on the dependency for the persistence package in the pom.xml file
//That  dependency must be there in order for the class to see this.
import javax.persistence.*;      //This includes the Id, GeneratedValue and GeneratedType class used below.

@Entity
public class Car 
{

@Id //The primary key is defined by using the @Id annotation
@GeneratedValue(strategy=GenerationType.AUTO) //defines that the ID # is automatically generated by the database
private long id;
private String brand, model, color, registerNumber;
private int year, price;
private Owner owner;

/*
//The following two lines define a many to one relationship from car to owner
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "owner")
*/

    // If you want to use some other naming convention, use the @Column annotation
    // This will let you also define the columns length and whether the column is nullable

    /* example:
    @Column(name="desc", nullable=false, length=512)
    private String description;
    */

        public Car(String brand, String model, String color, String registerNumber, int year, int price, Owner owner) 
{
    //This is an auto-genreated constructor
    super();
    this.brand = brand;
    this.model = model;
    this.color = color;
    this.registerNumber = registerNumber;
    this.year = year;
    this.price = price;
    this.owner = owner;
}

    //The following four lines create a many to many relationship in the     cars/owners tables
    @ManyToMany(mappedBy =  "cars")
    private Set<Owner> owners;
    public Set<Owner> getOwners(){return owners;}
    public void setOwners(Set<Owner> owners) {this.owners = owners;}


public Owner getOwner() {
    return owner;
}


public void setOwner(Owner owner) {
    this.owner = owner;
}



//The following are all auto-genreated getters and setters
public String getBrand() {
    return brand;
}

public void setBrand(String brand) {
    this.brand = brand;
}

public String getModel() {
    return model;
}

public void setModel(String model) {
    this.model = model;
}

public String getColor() {
    return color;
}

public void setColor(String color) {
    this.color = color;
}

public String getRegisterNumber() {
    return registerNumber;
}

public void setRegisterNumber(String registerNumber) {
    this.registerNumber = registerNumber;
}

public int getYear() {
    return year;
}

public void setYear(int year) {
    this.year = year;
}

public int getPrice() {
    return price;
}

public void setPrice(int price) {
    this.price = price;
}
}

CarRepository.java

package com.packt.cardatabase.domain;

import java.util.List; //allows the list keyword

import org.springframework.data.jpa.repository.Query;//Allows the use of the Query annotation
import org.springframework.data.repository.CrudRepository;


public interface CarRepository extends CrudRepository<Car, Long> 
{

//The following are all custom queries:

//Fetch Cars by color
List<Car> findByColor(String color);
//Fetch Cars by year
List<Car> findByYear(int year);

//Fetch Cars by brand and model
List<Car> findByBrandAndModel(String Brand, String Model);

//Fetch cars by brand using SQL using the @Query annotation.
//Remember to include the Query class in the imports above.
@Query("Select c from Car c where c.brand = ?1")
List<Car> findByBrand(String bran);

}

Owner.java

package com.packt.cardatabase.domain;

import javax.persistence.*;

import java.util.*;


@Entity
public class Owner 
{
@Id //The primary key is defined by using the @Id annotation
@GeneratedValue(strategy=GenerationType.AUTO) //defines that theID is automatically generated by the database
private long ownerid;
private String firstname, lastname;


//The following  line creates a One to many relationship between Owner and Car
//The cascade attribute means that if the owner is deleted, all linked cars are deleted too.
//The mappedBy="owner" attribute means that the car class has the owner field which is the foreign key for the relationship.
//@OneToMany(cascade = CascadeType.ALL, mappedBy="owner")
//private List<Car> cars;

    public Owner() {}
    //The following was auto generated using source -> Generate Constructor using fields
/**
 * @param ownerid
 * @param firstname
 * @param lastname
 */
    public Owner(String firstname, String lastname) 
    {
    super();
        this.firstname = firstname;
        this.lastname = lastname;
    }

    //The next 3 lines create a many to many relationship and join the columns 'id' and 'owner_id' to a new table called 'car_owner'.
        @ManyToMany(cascade = CascadeType.MERGE)
        @JoinTable(name = "car_owner", joinColumns = {@JoinColumn(name = "ownerid")}, inverseJoinColumns = {@JoinColumn(name = "id")})
    private Set<Car> cars = new HashSet<Car>(0);


    public Set<Car> getCars(){return cars;}
    public void setCars(Set<Car> cars) {this.cars = cars;}  

/*
public List<Car> getCars(){
    return cars;
}

public void setCars(List<Car> cars) {
    this.cars = cars;
}
*/


//The following are auto-generated getters and setters with comments

/**
 * @return the ownerid
 */
public long getOwnerid() {
    return ownerid;
}

/**
 * @param ownerid the ownerid to set
 */
public void setOwnerid(long ownerid) {
    this.ownerid = ownerid;
}

/**
 * @return the firstname
 */
public String getFirstname() {
    return firstname;
}

/**
 * @param firstname the firstname to set
 */
public void setFirstname(String firstname) {
    this.firstname = firstname;
}

/**
 * @return the lastname
 */
public String getLastname() {
    return lastname;
}

/**
 * @param lastname the lastname to set
 */
public void setLastname(String lastname) {
    this.lastname = lastname;
}

}

OwnerRepository.java

package com.packt.cardatabase.domain;

import org.springframework.data.repository.CrudRepository;

public interface OwnerRepository extends CrudRepository<Owner, Long> 
{}

Структура файлов в Eclipse:

enter image description here

0
MGT 16 Сен 2018 в 04:04

2 ответа

Лучший ответ

Либо удалите поле «Владелец», либо отметьте его как @Transient, либо создайте для него связь ManyToOne / OneToMany / OneToOne. Это твоя проблема.

1
Redmancometh 16 Сен 2018 в 02:22

Проблема с вашей сущностью Car, если вы определили ManyToOne и ManyToMany для сущности Owner, вы можете исправить это, удалив ниже из своей сущности Car.

private Owner owner;

Следует избегать двунаправленного сопоставления, поскольку иногда сложно управлять им и создавать проблемы с памятью, поскольку оба направленных объекта должны загружаться даже (можно управлять, создавая только требуемые данные для запроса или ленивую загрузку), вам когда-то не нужно, поэтому создание отдельного встроенного объекта идентификатора является отличная идея.

Вот как вы можете с этим справиться.

 @Entity
 public class CarOwner implements Serializable {

    @EmbeddedId
    private CarOwneId id;

    @ManyToOne
    @JoinColumn(name = "car_id", referencedColumnName = "car_id", insertable = false, updatable = false)
    private Car car;

    @ManyToOne
    @JoinColumn(name = "owner_id", referencedColumnName = "owner_id", insertable = false, updatable = false)
    private Owner owner;


    public CarOwner(Car c, Owner o) {
        // create primary key
        this.id = new CarOwneId(c.getId(), o.getOwnerid());

        // initialize attributes
        this.car = c;
        this.owner = o;
    }

    public CarOwneId getId() {
        return id;
    }

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

    public Car getCar() {
        return car;
    }

    public void setCar(Car car) {
        this.car = car;
    }

    public Owner getOwner() {
        return owner;
    }

    public void setOwner(Owner owner) {
        this.owner = owner;
    }

    @Embeddable
    public static class CarOwneId implements Serializable {

        @Column(name = "car_id")
        protected Long carId;

        @Column(name = "owner_id")
        protected Long ownerId;

        public CarOwneId() {

        }

        public CarOwneId(Long carId, Long ownerId) {
            this.carId = carId;
            this.ownerId = ownerId;
        }

        public Long getCarId() {
            return carId;
        }

        public void setCarId(Long carId) {
            this.carId = carId;
        }

        public Long getOwnerId() {
            return ownerId;
        }

        public void setOwnerId(Long ownerId) {
            this.ownerId = ownerId;
        }
    }
}
1
halfer 4 Ноя 2018 в 22:30