-
-
Notifications
You must be signed in to change notification settings - Fork 270
Description
Hi. It is very useful for us domain-driven freaks to be able to do the following:
@Entity
@Table(name = "t_user")
public class User {
@Id
private Long id;
@Column
private String name;
@Embedded
private final Address address = new Address();
//getters, setters
}
Where Address:
@Embeddable
public class Address {
@Column
private String streetName;
@Column
private String zipCode;
}
Suppose now the following MCVE:
public class Main {
public static void main(String[] args) {
Database database = createDatabase();
User user = new User();
user.setName("John Doe");
database.save(user);
user = database.find(User.class, 1L);
System.out.println(user);
System.out.println(user.getAddress()); //prints null!
}
private static Database createDatabase() {
DatabaseConfig config = new DatabaseConfig();
config.setName("playground");
config.addPackage(User.class.getPackageName());
config.setDdlCreateOnly(true);
config.setDdlGenerate(true);
config.setDdlRun(true);
// Configure data source
DataSourceConfig dsConfig = new DataSourceConfig();
dsConfig.setUsername("sa"); // H2 default system admin username is 'sa'
dsConfig.setPassword("");
dsConfig.setUrl("jdbc:h2:mem:playground;DB_CLOSE_DELAY=-1");
dsConfig.setDriver("org.h2.Driver");
config.setDataSourceConfig(dsConfig);
return DatabaseFactory.create(config);
}
}
When I create a new User, the Address is always non-null due to basic Java. However, when I load back the User object the Address is null because ALL FIELDS of address are null. It would be convinient to be able to do something like:
@io.ebean.Embedded(allowNullRef = false)
private final Address address = new Address();
Where in this case, Ebean ensures that address embeddable field will never be null.
Of course I tried many ways to work around it. Let's say I give up on the final and do the following:
@Embedded
private Address address = new Address();
public Address getAddress() {
if (address == null) {
address = new Address();
}
return address;
}
But in this case, if I do the following:
user = database.find(User.class, 1L);
System.out.println(user);
System.out.println(user.getAddress());//prints non-null, and thats ok
System.out.println(user.getAddress().getStreetName());//prints null, ofc thats ok
database.update(user);
Then, Ebean will detect the address as a "new" reference. Thus, Ebean thinks that the object is dirty and the update will do an actual update. However, in reality nothing was changed, other than the Address reference where the getter could be called even just for reading as shown in the example.
I tried to do the same with things like PostLoad etc, but those things are trickier to work with in the first place when other frameworks are also involved into a real application. Other workaround I found, but I really don't like is the following:
@Embedded
private final Address address = new Address();
And then:
@Embeddable
public class Address {
@Column
private String streetName;
@Column
private String zipCode;
@Column
private final int jpa_workaround = 0;
}
And now Ebean dirty check works:
07:11:22.965 [main] DEBUG io.ebean.internal - Update skipped as bean is unchanged: User@0(id:1, name:John Doe, address:Address@1(jpa_workaround:0))
What do you think? Would it be possible? I think I could even try to contribute and implement the change myself. Is this discussed already maybe? Because I could not find any "legit" information upon it. Is there maybe a workaround I did not think of? Let me know...
Thanks!
P.S: This is what I use:
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>17.1.1</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>17.1.1</version>
</dependency>