Проблема обновления сущности с отношением OneToMany при использовании составного первичного ключа

#java #spring-boot #jpa #one-to-many

Вопрос:

У меня есть две таблицы в моей схеме (схемах) базы данных PostgreSQL, a и b . a таблица содержит один столбец с именем a , который является первичным ключом для этой таблицы. Таблица b содержит два столбца, a и b . a столбец в таблице b -это внешний ключ, подключенный к a ключу в a таблице. b столбец менее важен, он содержит случайный текст. Таблица a и таблица b находятся в отношениях «один ко многим», то есть сущность в таблице a может быть связана с нулем со многими сущностями в таблице b , а сущность в таблице b может быть связана только с одной сущностью в таблице a . Таблица b не имеет первичного ключа.

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

Теперь я новичок в Spring Boot и интерфейсе программирования приложений с сохраняемостью Java (JPA), и я попытался представить эти две таблицы в классах java: a :

 package P;
@javax.persistence.Entity()
@javax.persistence.Table(schema = "s")
public class a implements java.io.Serializable
{
    @javax.persistence.Id()
    int a;

    @javax.persistence.OneToMany(fetch = javax.persistence.FetchType.EAGER, mappedBy = "l")
    java.util.List<P.b> l;
}
 

и b :

 package P;
@javax.persistence.Entity()
@javax.persistence.Table(schema = "s")
@javax.persistence.IdClass(value = b.class)
public class b implements java.io.Serializable
{
    @javax.persistence.Id()
    int a;
    @javax.persistence.Id()
    String b;

    @javax.persistence.ManyToOne()
    @javax.persistence.JoinColumn(insertable = false, name = "a", updatable = false)
    a l;
}
 

Теперь класс a содержит поле l , в котором я хотел бы иметь от нуля до многих b объектов, и оно снабжено OneToMany аннотациями, поскольку одна a сущность может быть связана со многими b сущностями.

Поскольку таблица b не содержит первичного ключа, а для загрузки Spring требуется первичный ключ, чтобы иметь возможность реализовать репозиторий, я решил создать виртуальный (не определенный в базе данных) составной первичный ключ, который состоит из всех полей в b классе. То есть я использую объект itsels в качестве первичного ключа. Поэтому я снабдил b класс IdClass аннотацией, чтобы Spring Boot мог рассматривать всю сущность в качестве первичного ключа.

Кроме того, в b классе есть поле l , в котором я хотел бы сохранить одну a сущность, к которой подключена конкретная b сущность. Поэтому я снабдил его ManyToOne аннотацией, поскольку многие b сущности могут быть связаны с одной a сущностью.

Затем я определил репозитории для Spring Boot для использования следующим образом: AR репозиторий для a сущностей:

 package P;
public interface AR extends org.springframework.data.repository.PagingAndSortingRepository<P.a, java.lang.Integer>{}
 

and BR repository for b entities:

 package P;
public interface BR extends org.springframework.data.repository.PagingAndSortingRepository<P.b, P.b>{}
 

Here I declared an entity itself as a primary key.

This works without any problems only for the a entity. If I attempt to operate on entity b an error is thrown. There are a few different errors which come up depending on what I try to do.

For example, if I try to add new row in b entity as follows:

 package P;
@org.springframework.boot.autoconfigure.SpringBootApplication()
public class M
{
    public final static void main(java.lang.String[] n)
    {
        org.springframework.boot.SpringApplication.run(M.class, "");
    }
    @org.springframework.beans.factory.annotation.Autowired()
    public void T(BR R)
    {
        b b = new b();
        b.a = 1;
        b.b = "T";
        R.save(b);
    }
}
 

this does not work. It throws following exception:

 Error creating bean with name 'm': Injection of autowired dependencies failed; nested exception is: could not insert: [P.b]; SQL [insert into s.b (b, a) values (?, ?)]; nested exception is: could not insert: [P.b]; Caused by: The column index is out of range: 3, number of columns: 2.
 

Now, the index out of range promped me to catch the SQL statement the Spring Boot generates in the server log, and here is the actual SQL statement which is sent to the server:

 BEGIN
select b0_.b as b1_1_0_, b0_.a as a2_1_0_ from s.b b0_ where b0_.b=$1 and b0_.a=$2 and b0_.a=$3
DETAIL:  parameters: $1 = 'T', $2 = '1', $3 = NULL
ROLLBACK
 

What is strange here is, first of all INSERT statement is not sent to the server at all (like what is written in the error message which spring boot produces) and second, strange part is WHERE part of the statement where it specifies three conditions instead of two. It specifies b.a (or b0.a as Spring Boot likes to call it) twice. Maybe that is what is causing the index out of range error. I think that it might tries to include the third property of the b class (named l ) in the queries despite it is annotated with JoinColumn where it is explicitely stated that this column should not be included in the INSERT nor UPDATE statements.

Obviously, the statement logged is the SELECT statement, not INSERT or UPDATE . So, how can I direct the Spring Boot not to include this third virtual column in the SELECT statement as well since there is no selectable parameter in JoinColumn annotation? If we look at the b class, we can see that I only annotated fields a and b with Id annotation, so, field l should not be seen by the Spring Boot as a column in the table.

Interestly, if I execute this query it does not return errors, but, it, however, does not select anything from the table. If I remove the third condition from the statement then it works as expected.

So it looks like an actual INSERT statement is never sent to the server, Spring Boot generates error before this happens, so I can not look into server logs to see what statement is actually generated. But I suppose that it is something of the similar form, that is, that it tries to insert three instead of two columns.

Just for information, both tables with their columns exist in the database and my application can access them (for example, I can access them if I send the query written above directly to the server from my application via Java DataBase Connectivity’s method for executing queries.).

I suspect that something is wrong with my b class. But what? I suspect that IdClass annotation is causing problems but I do not know what is wrong exactly.
I am sorry if this may be a beginner’s mistake but this is the first time EVER that I am trying to create OneToMany relationship in Java Persistence ApplicationProgrammingInterface.

I have been trying to debug this piece of code for the whole day now and I would appreciate any point about what did I do wrong.