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

Templates: The oneToOneJpaAnnotation should be orphanRemoval if the referenced-to object is not an aggregateRoot #175

Open
zhongweijun opened this issue Apr 27, 2015 · 9 comments

Comments

@zhongweijun
Copy link

First of all, if I am not wrong, the property settings of "cascade.aggregate" and "cascade.aggregate.oneToMany" in the sculptor-generator.properties file doesn't take effect at all in the lastest versions of SculptorGenerator.

And then, in the DomainObjectReferenceAnnotationTmpl file, the orphanRemoval strategies for OneToOne relationships and OneToMany relationships are quite different for referenced objects which are not aggregateRoot. For OneToMany relationships, the orphanRemoval is marked true for non-aggregateRoot, But for OneToOne relationships, the orphanRemoval is marked as false by default.

I think it's better to keep their default behavior same (that is, orphanRemovel=true for non-aggregateRoot objects).

Below is the original code from DomainObjectReferenceAnnotationTmpl class:

def String oneToOneJpaAnnotation(Reference it) {
    '''
        @javax.persistence.OneToOne(
            «formatAnnotationParameters(<Object>newArrayList(!nullable, "optional", false,
                isInverse(), "mappedBy", '"' + opposite.name + '"',
                it.getCascadeType() != null, "cascade", it.getCascadeType(),
                isOrphanRemoval(it.getCascadeType()), "orphanRemoval", true,
                it.getFetchType() != null, "fetch", it.getFetchType()
            ))»)
        «IF !isInverse()»
            @javax.persistence.JoinColumn(
            «formatAnnotationParameters(<Object>newArrayList(true, "name", '"' + it.getDatabaseName() + '"',
                !isJpaProviderOpenJpa() && !nullable, "nullable", false,
                it.isSimpleNaturalKey(), "unique", "true"
            ))»)
            «IF isJpaProviderHibernate()»
                @org.hibernate.annotations.ForeignKey(name = "FK_«truncateLongDatabaseName(from.getDatabaseName(), it.getDatabaseName())»")
                «IF it.getHibernateFetchType() != null»
                    @org.hibernate.annotations.Fetch(«it.getHibernateFetchType()»)
                «ENDIF»
            «ENDIF»
        «ENDIF»
    '''
}

def String oneToManyJpaAnnotation(Reference it) {
    '''
        @javax.persistence.OneToMany(
            «formatAnnotationParameters(<Object>newArrayList(it.getCascadeType() != null, "cascade", it.getCascadeType(),
                isOrphanRemoval(it.getCascadeType(), it), "orphanRemoval", true,
                it.hasOpposite() && (getCollectionType() != "list"), "mappedBy", '"' + opposite?.name + '"',
                it.getFetchType() != null, "fetch", it.getFetchType()
            ))»)
        «IF isJpaProviderHibernate() && !isInverse()»
            @org.hibernate.annotations.ForeignKey(
                name = "FK_«truncateLongDatabaseName(it.getManyToManyJoinTableName(), it.getOppositeForeignKeyName())»"
                , inverseName = "FK_«truncateLongDatabaseName(it.getManyToManyJoinTableName(), it.getForeignKeyName())»")
        «ENDIF»
        «/* TODO: add support for unidirectional onetomany relationships with and without jointable */»
        «/*
        «IF !it.isUnidirectionalToManyWithoutJoinTable() && isJpa2()»
            @javax.persistence.JoinTable(
                name = "«it.getManyToManyJoinTableName()»",
                joinColumns = @javax.persistence.JoinColumn(name = "«it.getOppositeForeignKeyName()»"),
                inverseJoinColumns = @javax.persistence.JoinColumn(name = "«it.getForeignKeyName()»"))
        «ENDIF»
         */»
        «IF isInverse() && (!it.hasOpposite() || it.isList())»
            @javax.persistence.JoinColumn(name = "«it.getOppositeForeignKeyName()»")
                «IF isJpaProviderHibernate()»
                    @org.hibernate.annotations.ForeignKey(name = "FK_«truncateLongDatabaseName(from.getDatabaseName(), to.getDatabaseName())»")
                «ENDIF »
        «ENDIF»
    '''
}
@zhongweijun zhongweijun changed the title Templates: The oneToOneJpaAnnotation should be orphanRemoval if the referenced to object is not aggregateRoot Templates: The oneToOneJpaAnnotation should be orphanRemoval if the referenced-to object is not an aggregateRoot Apr 27, 2015
tjuerge added a commit that referenced this issue May 10, 2015
… settings of "cascade.aggregate" and "cascade.aggregate.oneToMany" are working as expected
@tjuerge
Copy link
Member

tjuerge commented May 10, 2015

First of all, if I am not wrong, the property settings of "cascade.aggregate" and "cascade.aggregate.oneToMany" in the sculptor-generator.properties file doesn't take effect at all in the lastest versions of SculptorGenerator.

The test in 11bef26 shows that these settings are working as expected for referenced entities marked as non aggregate root (entities are aggregate roots by default). If you found a configuration which doesn't work as expected then please create a separate issue and attach the corresponding configuration.

@tjuerge
Copy link
Member

tjuerge commented May 11, 2015

Regarding Sculptors default settings Patrik wrote the following in a forum post as an answer to Pavels suggestion to change the default for cascade for one-to-one relations from
all to persist,merge:

Not possible to come up with a default that fits all situations.

....

Good thing is that anyone can easily define their own defaults.

So if you need a different default orphan removal strategy for one-to-one relations then specify cascade.aggregate=all-delete-orphan in your sculptor-generator.properties.

@zhongweijun
Copy link
Author

Hi Tjuerge,
I can give an example.

  1. The model design:
Application HelloWorld {
    basePackage = org.helloword

    Module user {
        Entity User {
            String userName key;
            - @EmailAddress officialEmailAddress;
            - List<@EmailAddress> privateEmailAddresses;
            - @Address officialAddress;
            - List<@Address> privateAddress;
        }


        ValueObject EmailAddress {
            String emailAddress;
        }

        ValueObject Address {
            !aggregateRoot
            String address;
        }
    }
}
  1. The property file:
project.nature=business-tier
cartridges=builder
generate.modeldoc=false
generate.umlgraph=false
cascade.aggregate=all-delete-orphan
  1. The generated result code:
    @ManyToOne(optional = false, cascade = CascadeType.ALL)
    @JoinColumn(name = "OFFICIALEMAILADDRESS", nullable = false)
    @ForeignKey(name = "FK_USER_OFFICIALEMAILADDRESS")
    @NotNull
    private EmailAddress officialEmailAddress;

    @ManyToOne(optional = false, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinColumn(name = "OFFICIALADDRESS", nullable = false)
    @ForeignKey(name = "FK_USER_OFFICIALADDRESS")
    @NotNull
    private Address officialAddress;

    @ManyToMany(cascade = CascadeType.ALL)
    @JoinTable(name = "PRIVATEEMAILADDRESS_USER", joinColumns = @JoinColumn(name = "USER"), inverseJoinColumns = @JoinColumn(name = "PRIVATEEMAILADDRESS"))
    @ForeignKey(name = "FK_PRIVATEEMAILADDRESS_USER_USER", inverseName = "FK_PRIVATEEMAILADDRESS_USER_PRIVATEEMAILADDRESS")
    @NotNull
    private List<EmailAddress> privateEmailAddresses = new ArrayList<EmailAddress>();

    @ManyToMany(cascade = CascadeType.ALL)
    @JoinTable(name = "PRIVATEADDRESS_USER", joinColumns = @JoinColumn(name = "USER"), inverseJoinColumns = @JoinColumn(name = "PRIVATEADDRESS"))
    @ForeignKey(name = "FK_PRIVATEADDRESS_USER_USER", inverseName = "FK_PRIVATEADDRESS_USER_PRIVATEADDRESS")
    @NotNull
    private List<Address> privateAddress = new ArrayList<Address>();
  1. remove cascade.aggregate=all-delete-orphan setting from the property file.

  2. The result code:

    @ManyToOne(optional = false, cascade = CascadeType.ALL)
    @JoinColumn(name = "OFFICIALEMAILADDRESS", nullable = false)
    @ForeignKey(name = "FK_USER_OFFICIALEMAILADDRESS")
    @NotNull
    private EmailAddress officialEmailAddress;

    @ManyToOne(optional = false, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinColumn(name = "OFFICIALADDRESS", nullable = false)
    @ForeignKey(name = "FK_USER_OFFICIALADDRESS")
    @NotNull
    private Address officialAddress;

    @ManyToMany(cascade = CascadeType.ALL)
    @JoinTable(name = "PRIVATEEMAILADDRESS_USER", joinColumns = @JoinColumn(name = "USER"), inverseJoinColumns = @JoinColumn(name = "PRIVATEEMAILADDRESS"))
    @ForeignKey(name = "FK_PRIVATEEMAILADDRESS_USER_USER", inverseName = "FK_PRIVATEEMAILADDRESS_USER_PRIVATEEMAILADDRESS")
    @NotNull
    private List<EmailAddress> privateEmailAddresses = new ArrayList<EmailAddress>();

    @ManyToMany(cascade = CascadeType.ALL)
    @JoinTable(name = "PRIVATEADDRESS_USER", joinColumns = @JoinColumn(name = "USER"), inverseJoinColumns = @JoinColumn(name = "PRIVATEADDRESS"))
    @ForeignKey(name = "FK_PRIVATEADDRESS_USER_USER", inverseName = "FK_PRIVATEADDRESS_USER_PRIVATEADDRESS")
    @NotNull
    private List<Address> privateAddress = new ArrayList<Address>();
  1. From above example, the cascade.aggregate=all-delete-orphan doesn't take effect.

Do you have any idea?

@tjuerge
Copy link
Member

tjuerge commented May 15, 2015

Do you have any idea?

I've no clue (I'm not using JPA).

But orphanRemovalis for @OneToOne and @OneToMany relations only (as described here). In your model the users cardinality is different (@ManyToOne and @ManyToMany) due to the two different types of relations used for the same ValueObject.

Btw. a ValueObject can't be a aggregate root, so !aggregateRootis redundant.

@zhongweijun
Copy link
Author

Yes Tjuerge. I think it's another issue that why such a model will be mapped to "ManyToOne" and "ManyToMany" relations? Did I lose some keyword/hints in this model?

BTW, what ORM framework are you using?

@tjuerge
Copy link
Member

tjuerge commented May 17, 2015

I think it's another issue that why such a model will be mapped to "ManyToOne" and "ManyToMany" relations?

Maybe this is due to the Users OneToOne (e.g. in "officialEmailAddress") and OneToMany (e.g. "privateEmailAddresses") relations to the same ValueObject (e.g. "EmailAddress"). This implies a ManyToMany relation between User and the corresponding ValueObject.

BTW, what ORM framework are you using?

I'm using Sculptor without any O/R mapper ("jpa.provider=none"). Depending on the backend the manually implemented repositories are using MyBatis, EJB or web services / REST.

@zhongweijun
Copy link
Author

Adopt the User model makes no sense. The model:

 Entity User {
            String userName key;
            - @EmailAddress officialEmailAddress;
            - List<@EmailAddress> privateEmailAddresses;
            - List<@Address> privateAddress;
        } 

The Result:

    @ManyToMany(cascade = CascadeType.ALL)
    @JoinTable(name = "PRIVATEADDRESS_USER", joinColumns = @JoinColumn(name = "USER"), inverseJoinColumns = @JoinColumn(name = "PRIVATEADDRESS"))
    @ForeignKey(name = "FK_PRIVATEADDRESS_USER_USER", inverseName = "FK_PRIVATEADDRESS_USER_PRIVATEADDRESS")
    @NotNull
    private Set<Address> privateAddress = new HashSet<Address>();

@zhongweijun
Copy link
Author

I tried bi-directional relationships as below. It leads to a OneToMany Relationship.
If for some reason I need to model a uni-directional OneToMany relationship, any idea?

        Entity User {
            String userName key;
            - @EmailAddress officialEmailAddress;
            - Set<@EmailAddress> privateEmailAddresses;
            - Set<@Address> privateAddresses <-> user;
            - @PhoneNumber phoneNumber;
            - Set<@PhoneNumber> phoneNumbers;
        }

        ValueObject Address {
            !aggregateRoot
            - @User user <-> privateAddresses;
            String address;
        }

Result:

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "user", fetch = FetchType.EAGER)
    @ForeignKey(name = "FK_PRIVATEADDRESS_USER_USER", inverseName = "FK_PRIVATEADDRESS_USER_PRIVATEADDRESS")
    @NotNull
    private Set<Address> privateAddresses = new HashSet<Address>();

@tjuerge
Copy link
Member

tjuerge commented May 18, 2015

I tried bi-directional relationships as below. It leads to a OneToMany Relationship.
If for some reason I need to model a uni-directional OneToMany relationship, any idea?

Quotes from the chapter on collections in the Advanced Tutorial:

Associations with cardinality “many” (Set, Bag, List) that are not bidirectional are by default generated as many-to-many, with a separate relation table.

:

By defining the reference as inverse it will be generated as an ordinary foreign key in the child table, i.e. an one-to-many relation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants