Różnica: Spring Data save() vs Hibernate save(). Niewypełniające się JoinTable

0

Posiadam encje

@Entity
public class Article {

	@Id
	@GeneratedValue(strategy = IDENTITY)
	private Long id;

	private String title;

	private String summary;

	private String content;

	@ManyToMany(fetch = FetchType.LAZY)
	@JoinTable(name = "article_statuses",
			joinColumns = @JoinColumn(name = "article_id", nullable = false),
			inverseJoinColumns = @JoinColumn(name = "status_id", nullable = false))
	private Set<ArticleStatus> statuses = new HashSet<>();
(...)

Enum:

public enum ArticleStatusName {
	NEW,
	ACCEPTED,
	EXPIRED,
	REMOVED

}

oraz drugą encję:

@Entity
@Table(name = "article_status")
public class ArticleStatus {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;

	@Enumerated(EnumType.STRING)
	@NaturalId
	@Column(length = 60)
	private ArticleStatusName name;
(...)
}

Mam dziwny problem...
Wywołując save na hibernate session nie wypełnia mi się tabela z @JoinTable, natomiast przy wykorzystaniu Spring Data i jego save() tabela się wypełnia. Czy ktoś jest w stanie mi wytłumaczyć w czym tkwi problem? Jaka jest w tym przypadku różnica pomiędzy save spring data a hibernate?

Różnica w wywoływanych queries:
hibernate:
screenshot-20190605005422.png
spring data:screenshot-20190605005500.png

1

Hmm, Nie mam nigdzie pod ręką kodu ze Spring Data żeby sprawdzić najpierw jak jest zaimplementowana metoda save. Różnica może wynikać z tego że nie masz nigdzie zdefiniowanej kaskadowości. Kaskadowość w takim dużym uproszczeniu definiuje co ma się dziać z rekordami które są w relacji do rekordu który próbujesz zapisać. Np. Jeśli zapisujesz Article to ArticleStatus też ma się zapisać. Jeśli usuniesz Article to czy ArticleStatus także ma być usuwane itd itd. Bez kaskadowości sam musisz zapisać najpierw dziecko a potem rodzica.
Trochę więcej znajdziesz tutaj:
https://stackoverflow.com/a/17087259/5877109

Myślę że taki fix powinien rozwiązać ten problem:

@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinTable(name = "article_statuses",
            joinColumns = @JoinColumn(name = "article_id", nullable = false),
            inverseJoinColumns = @JoinColumn(name = "status_id", nullable = false))
    private Set<ArticleStatus> statuses = new HashSet<>();

Ciekasza jest natomiast sprawa z czego wynika różnica między Spring Data a Hibernate. W wolnej chwili rzucę okiem na implementację i dokumentację.

0

No właśnie implementacja z kaskadą tutaj nie pomaga. Jeszcze raz sprawdzę, ale gdyby o taką drobnostkę chodziło to bym sobie sam poradził

0

Pokaż jak używasz tych metod. Używasz gdzieś @Transactional?

0

Ok, sprawa wygląda tak: Wczoraj jednak nie próbowałem z Cascade.ALL, ale próbowałem z hibernate-ową adnotacją @Cascade(org.hibernate.annotations.CascadeType.SAVE_UPDATE), nie zauważyłem zmian. Gdy ustawię CascadeType.ALL, wtedy save w ogóle nie commituje zmian do bazy danych, co więcej przy drugim insercie wyrzuca wyjątek

	@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
	@JoinTable(name = "article_statuses",
			joinColumns = @JoinColumn(name = "article_id", nullable = false),
			inverseJoinColumns = @JoinColumn(name = "status_id", nullable = false))
	private Set<ArticleStatus> statuses = new HashSet<>();

Wyjątek:

org.hibernate.NonUniqueObjectException: A different object with the same identifier value was already associated with the session : [com.drcloak.articles.status.ArticleStatus#1]

@CountZero używam w taki sposób:
Tworzenie sesji:

@Configuration
public class HibernateConfig {

	@Autowired
	private EntityManagerFactory entityManagerFactory;

	@Bean
	public Session getSession() {
		return entityManagerFactory.createEntityManager().unwrap(Session.class);
	}
}

Service:

@Service
@Transactional
public class NewArticleService {

	private final ArticleStatusRepository articleStatusRepository;
	private final ArticleRepository articleRepository;
	private final UserHelperService userHelperService;

	@Autowired
	public NewArticleService(ArticleRepository articleRepository, ArticleStatusRepository articleStatusRepository,
			UserHelperService userHelperService) {
		this.articleRepository = articleRepository;
		this.articleStatusRepository = articleStatusRepository;
		this.userHelperService = userHelperService;
	}

	public void createArticle(NewArticleDto newArticleDto) {
		User currentUser = userHelperService.getCurrentUser();
		ArticleStatus articleStatus = articleStatusRepository.findByName(NEW)
				.orElseThrow(() -> new InternalServerErrorException("Article Status not set."));

		Article article = new Article(newArticleDto.title, newArticleDto.summary, newArticleDto.content, currentUser);

		article.setStatuses(Collections.singleton(articleStatus));
		articleRepository.createArticle(article);
	}

}

Repository:

@Repository
@Transactional
public class ArticleRepository {

	private final Session session;

	@Autowired
	public ArticleRepository(Session session) {
		this.session = session;
	}

	public void createArticle(Article article) {
		session.save(article);
	}
(...)

Edit:
z kaskadą Persist nie tworzą się articles :(

Edit2:
Zmieniłem sposób tworzenia sesji, zaczęło działać bez żadnej kaskady. Mógłby mi ktoś wytłumaczyć różnicę? I dlaczego działa nawet bez kaskady?

@Repository
@Transactional
public class ArticleRepository {

	@PersistenceContext
	private EntityManager entityManager;


	public void createArticle(Article article) {
		getSession().save(article);
	}

	private Session getSession() {
		return entityManager.unwrap(Session.class);
	}
}

1 użytkowników online, w tym zalogowanych: 0, gości: 1