Milso Milso - 19 days ago 5
Java Question

Java - How to avoid creation of setter only for a particular class needs?

I am using Hibernate and currently using the setter to set the relation to parent in children at creation time (to avoid doing this manually for both sides). How I can avoid use of setter or avoid expose it to the rest of classes and get the same behaviour. Is it ok to use reflection? This is the code:

@Entity
@Table(name = "TEST_GROUP")
@Getter
public class TestGroupEntity extends AuditedEntity{

@ManyToOne
@JoinColumn(name = "owner", nullable = false)
protected UserEntity owner;

@Column(name = "description")
@Setter
protected String description;

@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
protected Set<TestEntity> tests = Sets.newHashSet();

public boolean addTest(TestEntity testEntity) {
return tests.add(testEntity);
}

public boolean removeTest(TestEntity testEntity) {
return tests.remove(testEntity);
}

public TestGroupEntity(UserEntity owner, Set<TestEntity> tests) {
this.owner = owner;
owner.setTestGroupEntity(this); ! how to avoid creation of setter
this.tests = tests;
tests.stream().forEach(t -> t.setTestGroupEntity(this)); ! how to avoid creation of setter
}
}


This is the children class ( I would like to keep immutability on api level):

@MappedSuperclass
@AllArgsConstructor
public class TestEntity extends AuditedEntity {

@Column(name = "name", nullable = false)
protected String name;

@Column(name = "description")
protected String description;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "test_group", nullable = false)
protected TestGroupEntity testGroupEntity;

public void setTestGroupEntity(TestGroupEntity testGroupEntity) {
this.testGroupEntity = testGroupEntity;
}
}


Edit: I think commented sections of code was not visible. Sorry.

Answer

How I can avoid use of setter or avoid expose it to the rest of classes and get the same behaviour. Is it ok to use reflection?

Of course you can for example reduce visibility of public setters to a visibility less wide than public in order that client classes of your entities cannot use them.
Which is in your case the real problem since accessing any data from inside the object is possible in anyway

From hibernate doc :

Attributes (whether fields or getters/setters) need not be declared public. Hibernate can deal with attributes declared with public, protected, package or private visibility. Again, if wanting to use runtime proxy generation for lazy loading the visibility for the getter/setter should be at least package visibility.

So, try to use private setter for desired field. It should address your problem.


Update After comment

You have several workarounds to address your problem :

  • using reflection (your basic idea).
    Drawback : it brings a little complexity, not a full check at compile-time and at last, someone who sees your code could wonder why you used that...
    It is the same thing for any concepts which relies on reflection such as AOP.

  • declaring these setters with package-private level and put the 3 classes in the same package.
    Drawback : the used package.

  • creating public init methods which raises an exception if it used more than once for a same object. In this way, you guarantee the coherence of the object if bad used.
    Drawback : method which should not be used by clients is still provided to clients.

Unfortunately, you have not a perfect solution since Java visibility mechanisms cannot provide a ready-to-use solution for what you are looking for.
Personally, I prefer reflection or init method solutions.

Personally, I noticed that in based-class languages as Java, a good developer has often the reflex to over- protect accessibility of objects/methods. In fact, in many cases, it is not needed because it will not break the application or data integrity.

Here an example with init method :

 public TestGroupEntity(UserEntity owner, Set<TestEntity> tests) {
        this.owner = owner;
        owner.constructInit(this); 
        this.tests = tests;
        tests.stream().forEach(t -> t.constructInit(this));  
    }

public class UserEntity {

    private TestGroupEntity testGroupEntity;

    public void constructInit(TestGroupEntity testGroupEntity) {
      if (this.testGroupEntity != null) {
        throw new IllegalArgumentException("forbidden");
      }
      this.testGroupEntity=testGroupEntity;
    }

}
Comments