2015-02-11 2 views
2

Я пытаюсь использовать Spring JPA/Hibernate для поддержки родительской таблицы с дочерней таблицей, связанной с идентификатором. Если я попытаюсь дважды вставить один и тот же объект, таблица родителя будет правильно обновлена, но дочерняя таблица всегда вставлена, даже если запись в дочерней таблице уже присутствует.Spring JPA/Hibernate @ManyToOne отношения всегда вставляют дочерние строки

CREATE TABLE `parent_table` (
    `parent_id` int(11) NOT NULL AUTO_INCREMENT, 
    `ex_column_1` int(11) NOT NULL, // there is a unique constraint on this field - I just didn't include it 
    `ex_column_2` int(11) NOT NULL, 
    `child_id` int(11) NOT NULL, 
    PRIMARY KEY (`parent_id`), 
) ENGINE=InnoDB DEFAULT CHARSET=latin1; 

Детский стол:

CREATE TABLE `child_table` (
    `child_id` int(11) NOT NULL AUTO_INCREMENT, 
    `child_col_1` varchar(200) DEFAULT NULL, 
    PRIMARY KEY (`child_id`) 
) ENGINE=InnoDB DEFAULT CHARSET=latin1; 

POJOs:

@Entity 
@Table(name = "parent_table") 
public final class Parent { 
    @GeneratedValue(strategy = GenerationType.AUTO) 
    @Column(name = "parent_id") 
    private long id; 

    @Id 
    @Column(name = "ex_column_1") 
    private int exampleField; 

    @Column(name = "ex_column_2") 
    private int exampleField2; 

    @ManyToOne(cascade = CascadeType.ALL) 
    @JoinColumn(name = "child_id", unique = true) 
    private Child c; 

    public Parent(/*params*/) {} 

    // getters ... 

    @Entity 
    @Table(name = "child_table") 
    public static final class Child { 
     @Id 
     @GeneratedValue(strategy = GenerationType.AUTO) 
     @Column(name = "child_id") 
     private long id; 

     @Column(name = "child_col_1") 
     private exampleChildField; 

     public Child(/*params*/) {} 

     // getters ... 
    } 
} 

Фактический пример того, как POJO-х построены и сохранены:

@RestController 
@RequestMapping("/parents") 
public final class ParentController { 
    private final ParentRepository parentRepository; 

    @Inject 
    public ParentController(final ParentRepository parentRepository) { 
     parentRepository = parentRepository; 
    } 

    @RequestMapping(value = "", method=RequestMethod.PUT) 
    public void updateParents(@RequestBody Parent[] parents) { 

     // ignore input for now, test by constructing object 

     Parent.Child c = new Parent.Child(0L, "test 1"); 
     Parent p = new Parent(0L, "unique_column_1", "column_2", c); 
     Set<Parent> pSet = new HashSet<>(); 
     pSet.add(p); 

     parentRepository.save(pSet); 
    } 
} 

Repository Layer:

public interface ParentRepository extends CrudRepository<Parent, Long>, ParentRepositoryCustom {} 

public interface ParentRepositoryCustom { 
    void save(Set<Parent> parents);  
} 

@Repository 
final class ParentRepositoryImpl implements ParentRepositoryCustom { 
    private final EntityManager entityManager; 

    @Inject 
    public EmployerRepositoryImpl(final EntityManager entityManager) { 
     this.entityManager = entityManager; 
    } 

    @Override 
    @Transactional 
    public void save(Set<Parent> parents) { 
     parents.forEach(parent -> { 
      Session session = (Session) entityManager.getDelegate(); 
      session.saveOrUpdate(parent); 
     }); 
    } 
} 

Если запись в какой-либо из таблиц не существует, она сохраняет оба сущности как можно точнее и связывает правильные идентификаторы между таблицами. Проблема возникает, если снова вставлены один и тот же родительский и дочерний объекты. UPDATE происходит на столе Родитель, но ВСТАВИТЬ на ребенка:

Hibernate: insert into child_table (child_col_1) values (?) 
Hibernate: insert into parent_table (ex_column_1, ex_column_2, child_id) values (?, ?, ?) 

На второй вставке:

Hibernate: insert into child_table (child_col_1) values (?) 
Hibernate: update parent_table set ex_column_2=?, child_id=? where ex_column_1=? 

Как получить EntityManager правильно упорствовать их, если запись уже существует?

+0

У вас есть странная база данных. Есть ли только один ребенок на одного родителя, и в этом случае он должен быть OneToOne not ManyToOne. – thedoctor

+0

Я рассмотрел пример как можно более простой, но в реальном случае может быть несколько записей родительской таблицы для записи в дочерней таблице. –

+0

Опубликуйте код, в котором вы создаете объекты, которые необходимо сохранить. –

ответ

0

Я думаю, что нашел проблему. Клиент, который по умолчанию ставит новые «родительские» объекты в интерфейс для остальных, устанавливает идентификаторы первичного ключа равными 0. Это работает для родителя, потому что ex_column_1 - это идентификатор, который известен в момент создания объектов каким-либо другим механизмом ,

С другой стороны, объект Child имеет только первичный ключ, а его значение равно нулю, Spring пытается найти связанную дочернюю строку на основе идентификатора нуля и всегда имеет вставку вместо обновить, если дочерняя строка уже существует. Простая проверка с использованием метода EntityManager.find() для обоих объектов, а затем сохранение или слияние исправлено.

Смежные вопросы