Objectify/AppEngine/Java: Many-many relationship - java

I use Objectify in AppEngine with JAva. I would like to model a many-many relationship in which the resolution entity has additional fields, as below
#Entity
public class Account {
public #Id Long id;
public String name;
}
#Entity
public class Baby {
#Id public Long id;
public String name;
}
#Entity
public class AccountBaby {
public #Id Long id;
#Index
#ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
public Ref<Account> account;
#Index
#ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
public Ref<Baby> baby;
public int permission;
}
If I follow this model, I can not query list of account for given baby's id (or list of baby for given account's id) as the query below
List<AccountBaby> babies = OfyService.ofy().load().type(AccountBaby.class).filter("account=",
Key.create(Account.class, accountId)).list();
Is they any other way to query or model this relationship?

You need a space in "account =". Or you can just leave off the "=", which is implied.
What you're actually searching for is a property which is literally named "account=" (which can be saved in the datastore with the low level api). This is mentioned in the javadocs for the filter() method.

Related

Spring Data JDBC - Many-to-One Relationship

I can't seem to find any reference online with regards to using a Many-To-One mapping in Spring JDBC. I just saw in the documentation that is not supported but I'm not sure if this is the case.
My example is that I want to map my AppUser to a particular Department.
For reference, AppUser joins to Department table using DEPARTMENT_ID
#Table(value="m_appuser")
public class AppUserProjectionTwo {
#Id
private Long id;
private String firstname;
private String middlename;
private String lastname;
#Column("DEPARTMENT_ID")
private DepartmentProjection departmenProjection;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
However, it seems that it won't map properly.
#Table("M_DEPARTMENT")
public class DepartmentProjection {
#Id
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
The created query looks like this. I was looking for something more of the opposite in which M_APPUSER.department_ID = Department.id
[SELECT "m_appuser"."ID" AS "ID", "m_appuser"."LASTNAME" AS "LASTNAME", "m_appuser"."FIRSTNAME" AS "FIRSTNAME", "m_appuser"."MIDDLENAME" AS "MIDDLENAME", "departmenProjection"."ID" AS "DEPARTMENPROJECTION_ID" FROM "m_appuser" LEFT OUTER JOIN "M_DEPARTMENT" AS "departmenProjection" ON "departmenProjection"."DEPARTMENT_ID" = "m_appuser"."ID" WHERE "m_appuser"."FIRSTNAME" = ?];
Thanks
I just saw in the documentation that is not supported but I'm not sure if this is the case.
I can confirm it is not supported.
Many-To-One relationships cross the boundaries of aggregates.
References across aggregates must be modelled as ids of the referenced aggregate.
If you don't do this Spring Data JDBC will consider the reference a One-To-One relationship and part of the same aggregate which will have effects you don't want for a Many-To-One relationship, like the referenced entity getting deleted when the referenced entity gets deleted. Which would be correct for a One-To-One relationship within the same aggregate.
This is explained in more detail in https://spring.io/blog/2018/09/24/spring-data-jdbc-references-and-aggregates

OneToMany Spring Data JDBC

I want to model a OneToMany Relation with Spring Data JDBC. I´ve read on this very useful blog https://spring.io/blog/2018/09/24/spring-data-jdbc-references-and-aggregates that you should use references when you want to model ToMany Reference:
Therefore any Many-to-One and Many-to-Many relationship must be modeled by just referencing the id.
So I have this scenario:
One Student can have multiple Registration. And one Registration can have exactly one Student. If you delete Registration the assigned Student should not get deleted cascading.
I ended up with this modelling:
#Data
#AllArgsConstructor(access = AccessLevel.PRIVATE, onConstructor = #__(#PersistenceConstructor))
public class Registration {
private final #Id
#Wither
long registrationId;
#NotNull
private String electiveType;
#NotNull
private LocalDateTime created = LocalDateTime.now();
#NotNull
private StudentRegistrationReference studentRegistrationReference;
}
#Data
#AllArgsConstructor(access = AccessLevel.PRIVATE, onConstructor = #__(#PersistenceConstructor))
public class StudentRegistrationReference {
private long student;
private long registration;
}
#Data
#AllArgsConstructor(access = AccessLevel.PRIVATE, onConstructor = #__(#PersistenceConstructor))
public class Student {
private final #Id
#Wither
long studentId;
#NotNull
#Size(min = 4, max = 20)
private String userId;
#NotNull
#Min(0)
private int matriculationNumber;
#NotNull
#Email
private String eMail;
private Set<StudentRegistrationReference> studentRegistrationReferences = new HashSet<>();
}
My question is whether my modeling is correctly implemented?
You are quoting the article talking about "Many-To-X" but you talk yourself about "X-To-Many". You can model a One-To-One or a One-To-Many relationship with a direct reference, or a List/Set/Map of entities.
What you should avoid are bidirectional relationships. While you probably can make them work with the approach you are using, you really shouldn't.
Which brings us to the question: How should this model look like?
The central decision to make is how many aggregates are involved?
A Student certainly is an aggregate and the Student class is its aggregate root. It can exist on its own.
But what about Registration? I'd argue, it is probably part of the same aggregate. The delete test is a good one. If you delete a Student from the system, do the registrations of that Student still have value? Or should the disappear together with the Student?
As an exercise let's do both variants. I start with: Just one aggregate:
class Registration {
#Id private long Id;
String electiveType;
LocalDateTime created = LocalDateTime.now();
}
class Student {
#Id private long Id;
String userId;
int matriculationNumber;
String eMail;
Set<Registration> registrations = new HashSet<>();
}
With this, you would have a single repository:
interface StudentRepository extends CrudRepository<Student, Long>{}
I removed all the Lombok annotations since they aren't really relevant to the problem. Spring Data JDBC can operate on simple attributes.
If Registration and Student both are aggregates it gets a little more involved:
You need to decide which side owns the reference.
First case: The Registration owns the reference.
class Registration {
#Id private long Id;
String electiveType;
LocalDateTime created = LocalDateTime.now();
Long studentId;
}
public class Student {
#Id private long Id;
String userId;
int matriculationNumber;
String eMail;
}
Second case: The Student owns the reference
class Registration {
#Id private long Id;
String electiveType;
LocalDateTime created = LocalDateTime.now();
}
class Student {
#Id private long Id;
String userId;
int matriculationNumber;
String eMail;
Set<RegistrationRef> registrations = new HashSet<>();
}
class RegistrationRef {
Long registrationId;
}
Note that the RegistrationRef doesn't have a studentId or similar. The table assumed for the registrations property will have a student_id column.

How to not return an specific column using PagingAndSortingRepository

I know the title may sound a bit confusing, but I did not know how to summarize my problem on the title.
My main problem is that I do not want to return an specific column using PagingAndSortingRepository. Imagine the following scenario:
I have two entitys, one called Users:
#Entity
#Table(name = "users")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank()
#Column(name = "name")
private String name;
#Column(name = "password")
private String password;
}
And one called Cars:
#Entity
#Table(name = "car")
public class Car implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank()
#Column
private String name;
#ManyToOne(targetEntity = User.class, fetch = FetchType.EAGER, cascade = CascadeType.MERGE)
#JoinColumn(name = "owner_id", referencedColumnName = "id")
private User owner;
}
I want to return a list of car by user's name using PagingAndSortingRepository. Using the following interface I can achieve that:
public interface CarRepository extends PagingAndSortingRepository<Car, Long> {
List<Car> findByOwner_name(String name);
}
However, with said interface the result will also return the user's password on it. I could use a for on each element of the list and set the password as null, but it would be troublesome in a scenario where there is a user with lots of cars. How could I make it so that the result of the list come with the specific column, password, without any value to the user?
Thanks in advance =).
ps: I thought about returning a list of projection instead of a list of Car, but I do not know how to say it to the projection that just one attribute from the class should be null.
You need to use Projections for this purpose. It is documented in detail here
Create an interface with fields you want to include in the entity to be returned.
For example, in your case it would be like this
interface CarSummary {
String getName();
Long getId();
UserSummary getUser();
interface UserSummary {
String getName();
Long getId();
}
}
and modify your repository like this
public interface CarRepository extends PagingAndSortingRepository<Car, Long> {
Collection<CarSummary> findByOwner_name(String name);
}
Projections can be used recursively. Something like this should work:
interface CarPojection{
Long getId();
String getName();
UserSummary getUser();
interface UserSummary {
Long getId();
String getName();
}
}
And your repository should return CarPojection
Find more information in Spring docs:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections
You can return a subset of data by providing a matching constructor in your entity class.
#Query("select new com.foo.bar.entity.User(u.id, u.name) from User u where u.name = ?1")
List<User> findByOwner_name(String name);
In you class User just provide the constructor and other properties will be left null:
public User(Long id, String name) {
// ...
}

Guidance on mapping the following relationship with ebean using jpa annotations

I have the following two entities in the DB ( structure is fixed ) which I am trying to map using JPA Annotations and EBEAN is ORM.
I have the following beans:
class Item {
public Long id;
public String name;
public Consignee intermediate;
public Consignee ultimate;
}
class Consignee {
public Long id;
public String name;
public String address;
public Item item;
}
And their corresponding tables:
Item
----
id
name
Consignee
---------
id:
name
address
item_id
type: [1,2] / 1: intermediate, 2:ultimate
the main entity is ITEM although the relationship is mapped from the consignee side.
How can I mapped this using the Annotations so that the consignees ( ultimate, intermediate ) are loaded when I fetch the Item object from DB ?
Could you please point me to the right direction
The two tables your are trying to map to Ebean are called Entity Models and the relation between Entity Consignee to Item is a One to Many Relationship.
Such relation can be mapped with a #OneToMany annotation on the Consignee side, and with an #ManyToOne on the Item side.
Also the field type of Consignee can be mapped with an Enumeration persisted as integer, and the remaining fields can be mapped via #Column annotation.
A possible implementation of your requirements could be something like:
public enum CONSIGNEE_TYPE {
INTERMEDIATE,
ULTIMATE
}
#Entity
#Table(name = "Consignee")
public class Consignee extends Model {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
public String name;
public String address;
#Enumerated(EnumType.ORDINAL)
public CONSIGNEE_TYPE type;
#Column(name="item_id")
#OneToMany(cascade = CascadeType.ALL, mappedBy = "consignee", fetch = FetchType.EAGER)
public List<Item> item = new ArrayList<Item>();
//TODO: Generate Constructors/Getters/Setters
}
#Entity
#Table(name = "Item")
public class Item extends Model {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
public String name;
#ManyToOne(optional = false, fetch = FetchType.EAGER)
public Consignee consignee = new Consignee();
//TODO: Generate Constructors/Getters/Setters
}
I removed the ambiguation with the ultimate and intermediate consignee type from the class Item since you can store the type of the consignee on the consignee itself.
Hope this helps.
As a very nice resource for the future, I recommend you to read the Unit Tests available on the source code of the Ebean itself. It ain't pretty but it helped me a lot!

Java Play: Nested models and persistence

I have nested models in a very simple Play application. I have a User model which looks like;
#Entity
public class User extends Model {
#Id
public Integer id;
#Constraints.Email
#Constraints.Required
public String email;
#Constraints.Required
private String password;
#ManyToOne
public City city;
}
And the City model looks like;
#Entity
public class City extends Model {
#Id
public Integer id;
public String name;
#ManyToOne
public Country country;
}
Which is again, very simple.
I then have the Country model, which is;
#Entity
public class Country extends Model {
#Id
public Integer id;
public String name;
}
Now, what I'm doing is POST-ing parameters email, password, and city_id to an action;
public static Result registerUser() {
Form<Register> registerForm = form(Register.class).bindFromRequest();
Logger.debug(registerForm.toString());
if (registerForm.hasErrors()) {
return badRequest(register.render(registerForm));
} else {
User user = form(User.class).bindFromRequest().get();
user.save();
return redirect(controllers.routes.Application.login());
}
}
The database I'm using is MySQL, and I can see the new User rows coming in. What I always see is that city_id stays null which wasn't what I had assumed.
I had assumed Hibernate to take care of the relationship between the objects and the corresponding database foreign keys, but that doesn't seem to be working.
I have a city with id = 1 entered into the city table already and that is the city_id I'm sending through POST.
What's going on here?

Categories

Resources