Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to persist cascade for non-null embedded id #1242

Merged
merged 1 commit into from
Dec 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,9 @@ private <T> T cascadeEntity(T entity, RuntimePersistentEntity<T> persistentEntit
if (persisted.contains(child)) {
continue;
}
boolean hasId = childPersistentEntity.getIdentity().getProperty().get(child) != null;
if (!hasId && (cascadeType == Relation.Cascade.PERSIST)) {
RuntimePersistentProperty<Object> identity = childPersistentEntity.getIdentity();
boolean hasId = identity.getProperty().get(child) != null;
if ((!hasId || identity instanceof Association) && (cascadeType == Relation.Cascade.PERSIST)) {
if (LOG.isDebugEnabled()) {
LOG.debug("Cascading PERSIST for '{}' association: '{}'", persistentEntity.getName(), cascadeOp.ctx.associations);
}
Expand Down Expand Up @@ -283,7 +284,7 @@ private <T> T cascadeEntity(T entity, RuntimePersistentEntity<T> persistentEntit
JdbcEntitiesOperations<Object> op = new JdbcEntitiesOperations<>(childPersistentEntity, cascadeManyOp.children);
op.veto(persisted::contains);
RuntimePersistentProperty<Object> identity = childPersistentEntity.getIdentity();
op.veto(e -> identity.getProperty().get(e) != null);
op.veto(e -> identity.getProperty().get(e) != null && !(identity instanceof Association));

persistInBatch(connection,
cascadeManyOp.annotationMetadata,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package io.micronaut.data.jdbc.h2

import io.micronaut.context.ApplicationContext
import io.micronaut.core.annotation.NonNull
import io.micronaut.data.annotation.Embeddable
import io.micronaut.data.annotation.Join
import io.micronaut.data.annotation.MappedEntity
import io.micronaut.data.jdbc.annotation.JdbcRepository
import io.micronaut.data.model.query.builder.sql.Dialect
import io.micronaut.data.repository.CrudRepository
import io.micronaut.test.extensions.spock.annotation.MicronautTest
import jakarta.inject.Inject
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

import javax.persistence.CascadeType
import javax.persistence.Column
import javax.persistence.EmbeddedId
import javax.persistence.GeneratedValue
import javax.persistence.Id
import javax.persistence.JoinColumn
import javax.persistence.ManyToOne
import javax.persistence.OneToMany

@MicronautTest
@H2DBProperties
class H2EmbeddedCascadeSpec extends Specification implements H2TestPropertyProvider {
@AutoCleanup
@Shared
ApplicationContext applicationContext = ApplicationContext.run(getProperties())

@Shared
@Inject
TemplateRepository templateRepository = applicationContext.getBean(TemplateRepository)

void "test embedded cascade"() {
when:
Template template = new Template()
template.name = "Template test"

Tag tag = new Tag()
TagPK tagPK = new TagPK()
tagPK.tag = "New tag"
tagPK.template = template
tag.id = tagPK

template.tags << tag

templateRepository.save(template)
template = templateRepository.findById(template.id).get()
then:
template
template.tags.size() == 1
}

}

@JdbcRepository(dialect = Dialect.H2)
interface TemplateRepository extends CrudRepository<Template, Long> {

@Join("tags")
@Override
Optional<Template> findById(Long aLong);
}

@MappedEntity
class Template {

@Id
@GeneratedValue
Long id

@NonNull
String name

@OneToMany(mappedBy = "id.template", cascade = CascadeType.ALL)
Set<Tag> tags = [] as Set

}

@MappedEntity
class Tag {

@EmbeddedId
TagPK id

}

@Embeddable
class TagPK implements Serializable {

@NonNull
@Column(name = "tag")
String tag

@ManyToOne
@JoinColumn(name = "template_id")
@Column(name = "template_id")
Template template

@Override
boolean equals(o) {
if (this.is(o)) {
return true
}
if (getClass() != o.class) {
return false
}

TagPK that = (TagPK) o

if (tag != that.tag) {
return false
}
return template == that.template
}

@Override
int hashCode() {
int result
result = tag.hashCode()
result = 31 * result + (template != null ? template.hashCode() : 0)
return result
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,11 @@ private <T> Mono<T> cascadeEntity(T en, RuntimePersistentEntity<T> persistentEnt
if (persisted.contains(child)) {
continue;
}
boolean hasId = childPersistentEntity.getIdentity().getProperty().get(child) != null;
RuntimePersistentProperty<Object> identity = childPersistentEntity.getIdentity();
boolean hasId = identity.getProperty().get(child) != null;

Mono<Object> childMono;
if (!hasId && (cascadeType == Relation.Cascade.PERSIST)) {
if ((!hasId || identity instanceof Association) && (cascadeType == Relation.Cascade.PERSIST)) {
if (LOG.isDebugEnabled()) {
LOG.debug("Cascading PERSIST for '{}' association: '{}'", persistentEntity.getName(), cascadeOp.ctx.associations);
}
Expand Down Expand Up @@ -291,7 +292,7 @@ private <T> Mono<T> cascadeEntity(T en, RuntimePersistentEntity<T> persistentEnt
R2dbcEntitiesOperations<Object> op = new R2dbcEntitiesOperations<>(childPersistentEntity, cascadeManyOp.children);
op.veto(persisted::contains);
RuntimePersistentProperty<Object> identity = childPersistentEntity.getIdentity();
op.veto(e -> identity.getProperty().get(e) != null);
op.veto(e -> identity.getProperty().get(e) != null && !(identity instanceof Association));

if (LOG.isDebugEnabled()) {
LOG.debug("Cascading PERSIST for '{}' association: '{}'", persistentEntity.getName(), cascadeOp.ctx.associations);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package io.micronaut.data.r2dbc.h2

import io.micronaut.context.ApplicationContext
import io.micronaut.core.annotation.NonNull
import io.micronaut.data.annotation.Embeddable
import io.micronaut.data.annotation.Join
import io.micronaut.data.annotation.MappedEntity
import io.micronaut.data.model.query.builder.sql.Dialect
import io.micronaut.data.r2dbc.annotation.R2dbcRepository
import io.micronaut.data.repository.reactive.ReactorCrudRepository
import io.micronaut.test.extensions.spock.annotation.MicronautTest
import jakarta.inject.Inject
import reactor.core.publisher.Mono
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

import javax.persistence.CascadeType
import javax.persistence.Column
import javax.persistence.EmbeddedId
import javax.persistence.GeneratedValue
import javax.persistence.Id
import javax.persistence.JoinColumn
import javax.persistence.ManyToOne
import javax.persistence.OneToMany

@MicronautTest
class H2EmbeddedCascadeSpec extends Specification implements H2TestPropertyProvider {
@AutoCleanup
@Shared
ApplicationContext applicationContext = ApplicationContext.run(getProperties())

@Shared
@Inject
TemplateRepository templateRepository = applicationContext.getBean(TemplateRepository)

void "test embedded cascade"() {
when:
Template template = new Template()
template.name = "Template test"

Tag tag = new Tag()
TagPK tagPK = new TagPK()
tagPK.tag = "New tag"
tagPK.template = template
tag.id = tagPK

template.tags << tag

templateRepository.save(template).block()
template = templateRepository.findById(template.id).blockOptional().get()
then:
template
template.tags.size() == 1
}

}

@R2dbcRepository(dialect = Dialect.H2)
interface TemplateRepository extends ReactorCrudRepository<Template, Long> {

@Join("tags")
@Override
Mono<Template> findById(Long aLong);
}

@MappedEntity
class Template {

@Id
@GeneratedValue
Long id

@NonNull
String name

@OneToMany(mappedBy = "id.template", cascade = CascadeType.ALL)
Set<Tag> tags = [] as Set

}

@MappedEntity
class Tag {

@EmbeddedId
TagPK id

}

@Embeddable
class TagPK implements Serializable {

@NonNull
@Column(name = "tag")
String tag

@ManyToOne
@JoinColumn(name = "template_id")
@Column(name = "template_id")
Template template

@Override
boolean equals(o) {
if (this.is(o)) {
return true
}
if (getClass() != o.class) {
return false
}

TagPK that = (TagPK) o

if (tag != that.tag) {
return false
}
return template == that.template
}

@Override
int hashCode() {
int result
result = tag.hashCode()
result = 31 * result + (template != null ? template.hashCode() : 0)
return result
}
}