Datafi auto-generates a powerful, user friendly data access manager for Spring-Data-Jpa applications.
- No more boilerplate JPaRepository interfaces.
- All of the features of Spring Data JPA without writing a single line of data layer code.
- Installation
- Hello World
- Archivability
- Custom Queries
- @FindBy, and @FindAllBy
- @FindByUnique
- Free text search * Domain model * Example Service Layer
- cascadedUpdate
- cascadeUpdateCollection
- Excluding fields from cascadeUpdate(...)
- Mutating the state of foreign key Iterables
Datafi is available on maven central:
<dependency>
<groupId>dev.sanda</groupId>
<artifactId>datafi</artifactId>
<version>0.1.0.BETA</version>
</dependency>
Datafi autogenerates Jpa repositories for all data model entities annotated with @Entity
and / or @Table
annotation(s).
To make use of this, @Autowire
the DataManager<T>
bean into your code, as follows:
@Entity
public class Person{
@Id
@GeneratedValue
private Long id;
private String name;
private Integer age;
}
Now any JpaRepository
or JpaSpecificationExecutor
method can be called. For example: findById(id)
@Service public class PersonService{
@Autowired
private DataManager<Person> personDataManager;
public Person getPersonById(String id){
return personDataManager.findById(id).orElse(...);
}
}
Records can be marked as archived, as opposed to actually deleting them from the database. Datafis' DataManager<T>
bean supports this out of the box with the following four methods:
public T archive(T input)
: Finds theinput
record by id, and marks it as archived.public T deArchive(T input)
: The opposite of 1.public List<T> archiveCollection(Collection<T> input)
: 1 in plural.public List<T> deArchiveCollection(Collection<T> input)
: 2 in plural.
In order to make use of this feature for a given entity, it must implement the Archivable
interface, which requires a getter and a setter for a Boolean isArchived
field.
Observe the following example:
@Entity
public class Person implements Archivable{
@Id
@GeneratedValue
private Long id;
private String name;
// if using lombok, use Boolean (not boolean) type
@Getter @Setter
private Boolean isArchived = false;
//...
}
@Service
public class PersonService{
@Autowired
private DataManager<Person> personDataManager;
public Person archivePerson(Person toArchive){
return personDataManager.archive(toArchive);
}
public Person deArchivePerson(Person toDeArchive){
return personDataManager.deArchive(toDeArchive);
}
public List<Person> archivePersons(List<Person> toArchive){
return personDataManager.archiveCollection(toArchive);
}
public List<Person> deArchivePersons(List<Person> toDeArchive){
return personDataManager.deArchiveCollection(toDeArchive);
}
}
The only thing capable of encapsulating the full power of SQL is SQL. That's why JpaRepository
includes the option of specifying custom SQL queries by annotating a method with a @Query(value = "...", nativeQuery = true / false)
annotation (see here for official documentation). Datafi accommodates for this as well, as follows.
The query syntax itself is identical to what it normally be. The two things which do require addressing are arguments and return types.
Query arguments are inferred from within the query itself. Arguments are specified as follows:
:<field name>
. For example, given our above Person
class; :id
, :name
, and is isArchived
would all be valid argument specifications.
<argument type>::<argument name>
. Argument type can be any JAVA primitive type. To specify a list, use <argument type>[]::<argument name>
. For example; to specify a single string type argument; String::nickName
. To specify a list of string type arguments; String[]::nickNames
.
If the argument name is identical to one which has been previously specified within the same query, use the syntax from case 1 (i.e. :<argument name>
).
This feature can be utilized via the following class level annotations:
-
@WithQuery(name = "METHOD_NAME_HERE", jpql = "JPQL_QUERY_HERE")
Example Model:@Entity @WithQuery(name = "findByNameAndAge", jpql = "SELECT p FROM Person p WHERE p.name = :name AND p.age = :age") public class Person { @Id @GeneratedValue private Long id; private String name; private Integer age; }
Resulting generated code:
@Repository public interface PersonDao extends GenericDao<Long, Person> { @Query("SELECT p FROM Person p WHERE p.name = :name AND p.age = :age") List<Person> findByNameAndAge(@Param("name") String name, @Param("age") Integer age); }
Breakdown:
When auto-generating the
JpaRepository
for a given entity, Datafi generates any custom methods that have been specified - which in this case would be the above@WithQuery(...)
annotation. The providedname
argument is used as the method name, and thejpql
argument is the query itself. -
@WithNativeQuery(name = "METHOD_NAME_HERE", sql = "NATIVE_SQL_QUERY_HERE")
Example Model:@Entity @WithNativeQuery(name = "findByNameAndAge", sql = "SELECT * FROM Person WHERE name = :name AND age = :age") public class Person { @Id @GeneratedValue private Long id; private String name; private Integer age; }
Resulting generated code:
@Repository public interface PersonDao extends GenericDao<Long, Person> { @Query("SELECT * FROM Person WHERE name = :name AND age = :age", nativeQuery = true) List<Person> findByNameAndAge(@Param("name") String name, @Param("age") Integer age); }
Breakdown is the same as the previous, except for the
nativeQuery
flag being set totrue
. -
@WithQueryScripts({"PATH/TO/SOME/FILE_NAME.jpql", "PATH/TO/SOME/OTHER/FILE_NAME.jpql", etc.})
The above two approaches don't scale very well, and are not ideal for longer queries. This and the next annotation(s) address this by allowing the developer to specify a list of files to be loaded as classpath resources (typically from the rootResources
directory). The name of each file is used in place of the abovename = "..."
parameter, and the file contents are of course used as the query body. This annotation is for non-nativejpql
queries, and as such all files specified must have a.jpql
type suffix. -
@WithNativeQueryScripts({"PATH/TO/SOME/FILE_NAME.sql", "PATH/TO/SOME/OTHER/FILE_NAME.sql", etc.})
Same as the previous, except for the queries being native. This means all files specified must have a.sql
type suffix.
Note regarding comments: All comments within queries must open with /*
and close with */
.
The default query return type is the annotated entity type itself. If the query is JPQL, it can leverage the dto projection feature in order to return a different type. Whether a single record or a list of records is returned is implied within the query itself. Queries beginning with INSERT
or REPLACE
are assumed to return a single record. Likewise, queries ending with LIMIT 1
are also assumed to return a single record. In all other cases, a list of records will be returned.
To use such a custom query, call the public <TResult> TResult callQuery(String queryName, Object... args)
method in DataManager<T>
. The queryName
is what it looks like, and the args
takes in the arguments in the order specified in the corresponding annotation (i.e. The order in which they are specified within the corresponding query itself).
Class field can be annotated with the @FindBy
and / or @FindAllBy
annotation(s), and this will generate a corresponding findBy...(value)
, or findAllBy...In(List<...> values)
. For example:
@Entity
public class Person{
@Id
private String id = UUID.randomUUID().toString();
@FindBy
private String name;
@FindAllBy
private Integer age;
@FindBy @FindAllBy
private String address;
// getters & setters, etc..
}
As can be observed, a field can have both annotations at the same time.
@Service
public class PersonService{
@Autowired
private DataManager<Person> personDataManager;
/* corresponds to @FindBy private String name;
returns a list of persons with the (same) given name */
public List<Person> getPersonsByName(String name){
return personDataManager.FindBy("name", name).orElse(...);}
//corresponds to @FindAllBy private Integer age;
public List<Person> getAllPersonsByAge(List<Integer> ages){
return personDataManager.FindAllBy("age", ages).orElse(...);}
//the following two methods correspond to @FindBy @FindAllBy private String address;
public List<Person> getPersonsByAddress(String address){
return personDataManager.FindBy("address", address).orElse(...);}
public List<Person> getAllPersonsByAddressIn(List<String> addresses){
return personDataManager.FindAllBy("address", addresses).orElse(...);}
}
@Entity
public class Person{
@Id
@GeneratedValue
private Long id;
@FindBy
private String name;
@FindAllBy
private Integer age;
@FindBy @FindAllBy
private String address;
// getters & setters, etc..
}
As can be observed, a field can have both annotations at the same time.
@Service public class PersonService{
@Autowired private DataManager<Person> personDataManager;
/* corresponds to @FindBy private String name;
returns a list of persons with the (same) given name */
public List<Person> getPersonsByName(String name){
return personDataManager.FindBy("name", name).orElse(...);}
//corresponds to @FindAllBy private Integer age;
public List<Person> getAllPersonsByAge(List<Integer> ages){
return personDataManager.FindAllBy("age", ages).orElse(...);}
//the following two methods correspond to @FindBy @FindAllBy private String address;
public List<Person> getPersonsByAddress(String address){
return personDataManager.FindBy("address", address).orElse(...);}
public List<Person> getAllPersonsByAddressIn(List<String> addresses){
return personDataManager.FindAllBy("address", addresses).orElse(...);}
}
As can be observed, the return type of both of the previous methods is a list. That's because there is no guarantee of uniqueness with regards to a field simply because it's been annotated with @FindBy
and / or @FindAllBy
. This is where @FindByUnique
differs; it takes a unique value argument, and returns a single corresponding entity. In order for this to be valid syntax, any field annotated with the @FindByUnique
annotation must also be annotated with @Column(unique = true)
. If a field is annotated with only @FindByUnique
but not @Column(unique = true)
, a compilation error will occur. The following is an illustrative example:
@Entity
public class Person{
@Id @GeneratedValue
private Long id;
@FindByUnique @Column(unique = true)
private String name;
//...
}
@Service
public class PersonService{
@Autowired
private DataManager<Person> personDataManager;
/*
corresponds to
@FindByUnique
private String name;
Returns a single person with the given name
*/
public Person getPersonByUniqueName(String name){
return personDataManager.FindByUnique( "name", name).orElse(...);
}
}
Datafi comes with non case sensitive free text ("Fuzzy") search out of the box. To make use of this, either one or more String typed fields can be annotated with @FreeTextSearchBy
, or the class itself can be annotated with @FreeTextSearchByFields({"field1", "field2", etc...})
. Then the freeTextSearchBy(String searchTerm, args...)
method in the respective class' DataManager
can be called.
Observe the following example:
@Entity
//@FreeTextSearchByFields({"name", "email"}) - this is equivalent to the field level annotations below
public class Person{
@Id @GeneratedValue
private Long id;
@FreeTextSearchBy
private String name;
@FreeTextSearchBy private String email;
//...
}
@Service public class PersonService{
@Autowired
private DataManager<Person> personDataManager;
public List<Person> freeTextSearchPeople(String searchTerm){
return personDataManager.freeTextSearch(searchTerm);
}
}
freeTextSearch
returns the listed contents of a Page
object. This means that the search results are paginated by definition. Because of this, freeTextSearch
takes in the 2 optional arguments int offset
and int limit
- in that order. These are "optional" in the sense that if not specified, the offset and limit will default to 0 and 50 respectively. An additional 2 optional arguments are String sortBy
and Sort.Direction sortDirection
- in that order. String sortBy
specifies the name of a field within the given entity by which to apply the sort. If no matching field is found an IllegalArgumentException
is thrown. Sort.Direction sortDirection
determines the ordering strategy. If not specified it defaults to ascending order (ASC
).
One issue which requires attention when designing a data model is cascading. Datafi simplifes this by offering out-of-the-box, built in application layer cascading when applying update operations. See illustration:
@Service public class PersonService{
@Autowired
private DataManager<Person> personDataManager;
public Person updatePerson(Person toUpdate, Person objectWithUpdatedValues){
return personDataManager.cascadeUpdate(toUpdate, objectWithUpdatedValues);
}
}
Breakdown:
-
The first argument is the
Person
instance we wish to update. -
The second argument is an instance of
Person
containing the updated values to be assigned to the corresponding fields within the firstPerson
instance. All of the it's other fields must be null.Important note: This method skips over any iterables.
cascadeUpdateCollection
offers analogous functionality to cascadeUpdate
, only in plural. For Example:
@Service public class PersonService{
@Autowired
private DataManager<Person> personDataManager;
//obviously, these two lists must correspond in length
public List<Person> updatePersons(List<Person> toUpdate, List<Person> objectsWithUpdatedValues){
return personDataManager.cascadeUpdateCollection(toUpdate, objectsWithUpdatedValues);
}
}
operations Field(s) to be excluded from cascadeUpdate
operations should be annotated as @NonApiUpdatable
. Alternately, if there are many such fields in a class and the developer would rather avoid the field-level annotational clutter, the class itself can be annotated with @NonApiUpdatables
, with the relevant field names passed as arguments. For example, the following:
@Entity
public class Person {
@Id
private String id = UUID.randomUUID().toString();
@NonApiUpdatable
private String name;
@NonApiUpdatable
private Integer age;
private String address;
}
is equivalent to:
@Entity
@NonApiUpdatables({"name", "age"}) public class Person {
@Id
@GeneratedValue
private Long id;
private String name;
private String address;
}
As metioned above, cascadeUpdate
operations skip over iterable type fields. This is due to the fact that collection mutations involve adding or removing elements to or from the collection - not mutations on the collection container itself. Therefore, DataManager<T>
includes the following methods to help with adding to, and removing from, foreign key collections.
public<HasTs> List<T> createAndAddNewToCollectionIn(HasTs toAddTo, String fieldName, List<T> toAdd)
This method takes in three arguments, while making internal use of the application levelcascadedUpdate
above in order to propogate the relevant state changes:HasTs toAddTo
- The entity containing the foriegn key collection of "Ts" (The type of entities referenced in the collection) to which to add.String fieldName
- The field name of the foreign key collection (i.e. forprivate Set<Person> friends;
, it'd be"friends"
).List<T> toAdd
- The entities to add to the collection.
public<HasTs> List<T> associateExistingWithCollectionIn(HasTs toAddTo, String fieldName, List<T> toAttach)
Similar to the previous method but for one crucial difference; it ensures the entities to be attached (not added from scratch) are indeed already present within their respective table within the database.
If you don't wan't to worry about assigning @Id
or @Version
column. the StandardPersistableEntity
@MappedSuperclass
can be extended. For example:
@Entity
public class Person extends StandardPersistableEntity {
private String name;
private Integer age;
// getters & setters, etc...
}
Alternately, the unix timestamp based Long IdFactory.getNextId()
static method can be employed. For example:
@Id
private Long id = IdFactory.getNextId();
private String name;
private Integer age;
// getters & setters, etc...
}
That's all for now, happy coding!
Apache 2.0# Datafi
Datafi auto-generates a powerful, user friendly data access manager for Spring-Data-Jpa applications.
- No more boilerplate JPaRepository interfaces.
- Custom Jpa resolvers with a few simple field-level annotations.
- Get all the features of Jpa for your entire data model, without writing a single line of data layer code.
Datafi is available on maven central:
<dependency>
<groupId>dev.sanda</groupId>
<artifactId>datafi</artifactId>
<version>0.0.3</version>
</dependency>
Datafi autogenerates Jpa repositories for all data model entities annotated with @Entity
and / or @Table
annotation(s).
To make use of this, @Autowire
the DataManager<T>
bean into your code, as follows:
@Entity
public class Person{
@Id
@GeneratedValue
private Long id;
private String name;
private Integer age;
}
Now any JpaRepository
or JpaSpecificationExecutor
method can be called. For example: findById(id)
@Service public class PersonService{
@Autowired
private DataManager<Person> personDataManager;
public Person getPersonById(String id){
return personDataManager.findById(id).orElse(...);
}
}
Sometimes when it comes to removing records from a database, the choice is made to mark the relevant records as archived, as oppposed to actually deleting them from the database. Datafi DataManager<T>
supports this out of the box with the following four methods:
public T archive(T input)
: Finds theinput
record by id, and marks it as archived.public T deArchive(T input)
: The opposite of 1.public List<T> archiveCollection(Collection<T> input)
: 1 in plural.public List<T> deArchiveCollection(Collection<T> input)
: 2 in plural.
In order to make use of this feature for a given entity, it must implement the Archivable
interface, the implementation of which requires a getter and a setter for a Boolean isArchived
field.
Observe the following example:
@Entity
public class Person implements Archivable{
@Id
@GeneratedValue
private Long id;
private String name;
// if using lombok, use Boolean (not boolean) type
@Getter @Setter
private Boolean isArchived = false;
//...
}
@Service
public class PersonService{
@Autowired
private DataManager<Person> personDataManager;
public Person archivePerson(Person toArchive){
return personDataManager.archive(toArchive);
}
public Person deArchivePerson(Person toDeArchive){
return personDataManager.deArchive(toDeArchive);
}
public List<Person> archivePersons(List<Person> toArchive){
return personDataManager.archiveCollection(toArchive);
}
public List<Person> deArchivePersons(List<Person> toDeArchive){
return personDataManager.deArchiveCollection(toDeArchive);
}
}
The only thing capable of encapsulating the full power of SQL is SQL. That's why JpaRepository
includes the option of specifying custom SQL queries by annotating a method with a @Query(value = "...", nativeQuery = true / false)
annotation (see here for official documentation).
The query syntax itself is identical to what it normally be. The two things which do require addressing are arguments and return types.
Query arguments are inferred from within the query itself. Arguments are specified as follows:
:<field name>
. For example, given our above Person
class; :id
, :name
, and is isArchived
would all be valid argument specifications.
<argument type>::<argument name>
. Argument type can be any JAVA primitive type. To specify a list, use <argument type>[]::<argument name>
. For example; to specify a single string type argument; String::name
. To specify a list of string type arguments; String[]::names
.
If the argument name is identical to one which has been previously specified within the same query, use the syntax from case 1 (i.e. :<argument name>
).
This feature can be utilized via the following class level annotations:
-
@WithQuery(name = "METHOD_NAME_HERE", jpql = "JPQL_QUERY_HERE")
Example Model:@Entity @WithQuery(name = "findByNameAndAge", jpql = "SELECT p FROM Person p WHERE p.name = :name AND p.age = :age") public class Person { @Id @GeneratedValue private Long id; private String name; private Integer age; }
Resulting generated code:
@Repository public interface PersonDao extends GenericDao<Long, Person> { @Query("SELECT p FROM Person p WHERE p.name = :name AND p.age = :age") List<Person> findByNameAndAge(@Param("name") String name, @Param("age") Integer age); }
Breakdown:
When auto-generating the
JpaRepository
for a given entity, Datafi generates any custom methods that have been specified - which in this case would be the above@WithQuery(...)
annotation. The providedname
argument is used as the method name, and thejpql
argument is the query itself. -
@WithNativeQuery(name = "METHOD_NAME_HERE", sql = "NATIVE_SQL_QUERY_HERE")
Example Model:@Entity @WithNativeQuery(name = "findByNameAndAge", sql = "SELECT * FROM Person WHERE name = :name AND age = :age") public class Person { @Id @GeneratedValue private Long id; private String name; private Integer age; }
Resulting generated code:
@Repository public interface PersonDao extends GenericDao<Long, Person> { @Query("SELECT * FROM Person WHERE name = :name AND age = :age", nativeQuery = true) List<Person> findByNameAndAge(@Param("name") String name, @Param("age") Integer age); }
Breakdown is the same as the previous, except for the
nativeQuery
flag being set totrue
. -
@WithQueryScripts({"PATH/TO/SOME/FILE_NAME.jpql", "PATH/TO/SOME/OTHER/FILE_NAME.jpql", etc.})
The above two approaches don't scale very well, and are not ideal for longer queries. This and the next annotation(s) address this by allowing the developer to specify a list of files to be loaded as classpath resources (typically from the rootResources
directory). The name of each file is used in place of the abovename = "..."
parameter, and the file contents are of course used as the query body. This annotation is for non-nativejpql
queries, and as such all files specified must have a.jpql
type suffix. -
@WithNativeQueryScripts({"PATH/TO/SOME/FILE_NAME.sql", "PATH/TO/SOME/OTHER/FILE_NAME.sql", etc.})
Same as the previous, except for the queries being native. This means all files specified must have a.sql
type suffix.
Any field can be annotated with the @FindBy
and / or @FindAllBy
annotation(s), and this will generate a corresponding findBy...(value)
, or findAllBy...In(List<...> values)
. For example:
@Entity
public class Person{
@Id
private String id = UUID.randomUUID().toString();
@FindBy
private String name;
@FindAllBy
private Integer age;
@FindBy @FindAllBy
private String address;
// getters & setters, etc..
}
As can be observed, a field can have both annotations at the same time.
@Service
public class PersonService{
@Autowired
private DataManager<Person> personDataManager;
/* corresponds to @FindBy private String name;
returns a list of persons with the (same) given name */
public List<Person> getPersonsByName(String name){
return personDataManager.FindBy("name", name).orElse(...);}
//corresponds to @FindAllBy private Integer age;
public List<Person> getAllPersonsByAge(List<Integer> ages){
return personDataManager.FindAllBy("age", ages).orElse(...);}
//the following two methods correspond to @FindBy @FindAllBy private String address;
public List<Person> getPersonsByAddress(String address){
return personDataManager.FindBy("address", address).orElse(...);}
public List<Person> getAllPersonsByAddressIn(List<String> addresses){
return personDataManager.FindAllBy("address", addresses).orElse(...);}
}
@Entity
public class Person{
@Id
@GeneratedValue
private Long id;
@FindBy
private String name;
@FindAllBy
private Integer age;
@FindBy @FindAllBy
private String address;
// getters & setters, etc..
}
As can be observed, a field can have both annotations at the same time.
@Service public class PersonService{
@Autowired private DataManager<Person> personDataManager;
/* corresponds to @FindBy private String name;
returns a list of persons with the (same) given name */
public List<Person> getPersonsByName(String name){
return personDataManager.FindBy("name", name).orElse(...);}
//corresponds to @FindAllBy private Integer age;
public List<Person> getAllPersonsByAge(List<Integer> ages){
return personDataManager.FindAllBy("age", ages).orElse(...);}
//the following two methods correspond to @FindBy @FindAllBy private String address;
public List<Person> getPersonsByAddress(String address){
return personDataManager.FindBy("address", address).orElse(...);}
public List<Person> getAllPersonsByAddressIn(List<String> addresses){
return personDataManager.FindAllBy("address", addresses).orElse(...);}
}
As can be observed, the return type of both of the previous methods is a list. That's because there is no guarantee of uniqueness with regards to a field simply because it's been annotated with @FindBy
and / or @FindAllBy
. This is where @FindByUnique
differs; it takes a unique value argument, and returns a single corresponding entity. In order for this to be valid syntax, any field annotated with the @FindByUnique
annotation must also be annotated with @Column(unique = true)
. If a field is annotated with only @FindByUnique
but not @Column(unique = true)
, a compilation error will occur. The following is an illustrative example:
@Entity
public class Person{
@Id @GeneratedValue
private Long id;
@FindByUnique @Column(unique = true)
private String name;
//...
}
@Service
public class PersonService{
@Autowired
private DataManager<Person> personDataManager;
/*
corresponds to
@FindByUnique
private String name;
Returns a single person with the given name
*/
public Person getPersonByUniqueName(String name){
return personDataManager.FindByUnique( "name", name).orElse(...);
}
}
Datafi comes with non case sensitive free text ("Fuzzy") search out of the box. To make use of this, either one or more String typed fields can be annotated with @FreeTextSearchBy
, or the class itself can be annotated with @FreeTextSearchByFields({"field1", "field2", etc...})
. Then the freeTextSearchBy(String searchTerm, args...)
method in the respective class' DataManager
can be called.
Observe the following example:
@Entity
//@FreeTextSearchByFields({"name", "email"}) - this is equivalent to the field level annotations below
public class Person{
@Id @GeneratedValue
private Long id;
@FreeTextSearchBy
private String name;
@FreeTextSearchBy private String email;
//...
}
@Service public class PersonService{
@Autowired
private DataManager<Person> personDataManager;
public List<Person> freeTextSearchPeople(String searchTerm){
return personDataManager.freeTextSearch(searchTerm);
}
}
freeTextSearch
does returns the listed contents of a Page
object. This means that the search results are paginated by definition. Because of this, freeTextSearch
takes in the 2 optional arguments int offset
and int limit
- in that order. These are "optional" in the sense that if not specified, the offset and limit will default to 0 and 50 respectively. An additional 2 optional arguments are String sortBy
and Sort.Direction sortDirection
- in that order. String sortBy
specifies the name of a field within the given entity by which to apply the sort. If no matching field is found an IllegalArgumentException
is thrown. Sort.Direction sortDirection
determines the ordering strategy. If not specified it defaults to ascending order (ASC
).
One issue which requires attention when designing a data model is cascading. Datafi simplifes this by offering out-of-the-box, built in application layer cascading when applying update operations. See illustration:
@Service public class PersonService{
@Autowired
private DataManager<Person> personDataManager;
public Person updatePerson(Person toUpdate, Person objectWithUpdatedValues){
return personDataManager.cascadedUpdate(toUpdate, objectWithUpdatedValues);
}
}
Breakdown:
-
The first argument is the
Person
instance we wish to update. -
The second argument is an instance of
Person
containing the updated values to be assigned to the corresponding fields within the firstPerson
instance. All of the it's other fields must be null.Important note: This method skips over any iterables.
cascadeUpdateCollection
offers analogous functionality to cascadeUpdate
, only in plural. For Example:
@Service public class PersonService{
@Autowired
private DataManager<Person> personDataManager;
//obviously, these two lists must correspond in length
public List<Person> updatePersons(List<Person> toUpdate, List<Person> objectsWithUpdatedValues){
return personDataManager.cascadeUpdateCollection(toUpdate, objectsWithUpdatedValues);
}
}
operations Field(s) to be excluded from cascadeUpdate
operations should be annotated as @NonApiUpdatable
. Alternately, if there are many such fields in a class and the developer would rather avoid the field-level annotational clutter, the class itself can be annotated with @NonApiUpdatables
, with the relevant field names passed as arguments. For example, the following:
@Entity
public class Person {
@Id
private String id = UUID.randomUUID().toString();
@NonApiUpdatable
private String name;
@NonApiUpdatable
private Integer age;
private String address;
}
is equivalent to:
@Entity
@NonApiUpdatables({"name", "age"}) public class Person {
@Id
@GeneratedValue
private Long id;
private String name;
private String address;
}
As metioned above, cascadeUpdate
operations skip over iterable type fields. This is due to the fact that collection mutations involve adding or removing elements to or from the collection - not mutations on the collection container itself. Therefore, DataManager<T>
includes the following methods to help with adding to, and removing from, foreign key collections.
public<HasTs> List<T> createAndAddNewToCollectionIn(HasTs toAddTo, String fieldName, List<T> toAdd)
This method takes in three arguments, while making internal use of the application levelcascadedUpdate
above in order to propogate the relevant state changes:HasTs toAddTo
- The entity containing the foriegn key collection of "Ts" (The type of entities referenced in the collection) to which to add.String fieldName
- The field name of the foreign key collection (i.e. forprivate Set<Person> friends;
, it'd be"friends"
).List<T> toAdd
- The entities to add to the collection.
public<HasTs> List<T> associateExistingWithCollectionIn(HasTs toAddTo, String fieldName, List<T> toAttach)
Similar to the previous method but for one crucial difference; it ensures the entities to be attached (not added from scratch) are indeed already present within their respective table within the database.
If you don't wan't to worry about assigning @Id
or @Version
column. the StandardPersistableEntity
@MappedSuperclass
can be extended. For example:
@Entity
public class Person extends StandardPersistableEntity {
private String name;
private Integer age;
// getters & setters, etc...
}
Alternately, the unix timestamp based Long IdFactory.getNextId()
static method can be employed. For example:
@Id
private Long id = IdFactory.getNextId();
private String name;
private Integer age;
// getters & setters, etc...
}
That's all for now, happy coding!