com.avaje.ebean.Model Maven / Gradle / Ivy
package com.avaje.ebean;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.util.ClassUtil;
import org.jetbrains.annotations.Nullable;
import javax.persistence.MappedSuperclass;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
/**
* A MappedSuperclass base class that provides convenience methods for inserting, updating and
* deleting beans.
*
*
* By having your entity beans extend this it provides a 'Active Record' style programming model for
* Ebean users.
*
*
* Note that there is a avaje-ebeanorm-mocker project that enables you to use Mockito or similar
* tools to still mock out the underlying 'default EbeanServer' for testing purposes.
*
*
* You may choose not use this Model mapped superclass if you don't like the 'Active Record' style
* or if you believe it 'pollutes' your entity beans.
*
*
* You can use Dependency Injection like Guice or Spring to construct and wire a EbeanServer instance
* and have that same instance used with this Model and Finder. The way that works is that when the
* DI container creates the EbeanServer instance it can be registered with the Ebean singleton. In this
* way the EbeanServer instance can be injected as per normal Guice / Spring dependency injection and
* that same instance also used to support the Model and Finder active record style.
*
*
* If you choose to use the Model mapped superclass you will probably also chose to additionally add
* a {@link Find} as a public static field to complete the active record pattern and provide a
* relatively nice clean way to write queries.
*
*
Typical common @MappedSuperclass
* {@code
*
* // Typically there is a common base model that has some
* // common properties like the ones below
*
* @MappedSuperclass
* public class BaseModel extends Model {
*
* @Id Long id;
*
* @Version Long version;
*
* @CreatedTimestamp Timestamp whenCreated;
*
* @UpdatedTimestamp Timestamp whenUpdated;
*
* ...
*
* }
*
* Extend the Model
* {@code
*
* // Extend the mappedSuperclass
*
* @Entity @Table(name="oto_account")
* public class Customer extends BaseModel {
*
* // Add a static Find
* // ... with Long being the type of our @Id property.
* // ... Note the {} at the end as Find is an abstract class.
*
* public static final Find find = new Find(){};
*
* String name;
* ...
* }
*
* }
*
* Modal: save()
* {@code
*
* // Active record style ... save(), delete() etc
* Customer customer = new Customer();
* customer.setName("AC234");
*
* // save() method inherited from Model
* customer.save();
*
* }
*
* Find byId
* {@code
*
* // find byId
* Customer customer = Customer.find.byId(42);
*
* }
*
* Find where
* {@code
*
* // find where ...
* List customers =
* Customer.find
* .where().gt("startDate", lastMonth)
* .findList();
*
* }
*/
@MappedSuperclass
public abstract class Model {
/**
* Return the underlying 'default' EbeanServer.
*
*
* This provides full access to the API such as explicit transaction demarcation etc.
*
*
* Example:
*
{@code
*
* Transaction transaction = Customer.db().beginTransaction();
* try {
*
* // turn off cascade persist for this transaction
* transaction.setPersistCascade(false);
*
* // extra control over jdbc batching for this transaction
* transaction.setBatchGetGeneratedKeys(false);
* transaction.setBatchMode(true);
* transaction.setBatchSize(20);
*
* Customer customer = new Customer();
* customer.setName("Roberto");
* customer.save();
*
* Customer otherCustomer = new Customer();
* otherCustomer.setName("Franko");
* otherCustomer.save();
*
* transaction.commit();
*
* } finally {
* transaction.end();
* }
*
* }
*/
public static EbeanServer db() {
return Ebean.getDefaultServer();
}
/**
* Return a named EbeanServer that is typically different to the default server.
*
*
* If you are using multiple databases then each database has a name and maps to a single
* EbeanServer. You can use this method to get an EbeanServer for another database.
*
* @param server
* The name of the EbeanServer. If this is null then the default EbeanServer is returned.
*/
public static EbeanServer db(String server) {
return Ebean.getServer(server);
}
/**
* Marks the entity bean as dirty.
*
* This is used so that when a bean that is otherwise unmodified is updated the version
* property is updated.
*
* An unmodified bean that is saved or updated is normally skipped and this marks the bean as
* dirty so that it is not skipped.
*
*
{@code
*
* Customer customer = Customer.find.byId(id);
*
* // mark the bean as dirty so that a save() or update() will
* // increment the version property
* customer.markAsDirty();
* customer.save();
*
* }
*
* @see EbeanServer#markAsDirty(Object)
*/
public void markAsDirty() {
db().markAsDirty(this);
}
/**
* Mark the property as unset or 'not loaded'.
*
* This would be used to specify a property that we did not wish to include in a stateless update.
*
* {@code
*
* // populate an entity bean from JSON or whatever
* User user = ...;
*
* // mark the email property as 'unset' so that it is not
* // included in a 'stateless update'
* user.markPropertyUnset("email");
*
* user.update();
*
* }
*
* @param propertyName the name of the property on the bean to be marked as 'unset'
*/
public void markPropertyUnset(String propertyName) {
((EntityBean)this)._ebean_getIntercept().setPropertyLoaded(propertyName, false);
}
/**
* Insert or update this entity depending on its state.
*
*
* Ebean will detect if this is a new bean or a previously fetched bean and perform either an
* insert or an update based on that.
*
* @see EbeanServer#save(Object)
*/
public void save() {
db().save(this);
}
/**
* Update this entity.
*
* @see EbeanServer#update(Object)
*/
public void update() {
db().update(this);
}
/**
* Insert this entity.
*
* @see EbeanServer#insert(Object)
*/
public void insert() {
db().insert(this);
}
/**
* Delete this bean.
*
* This will return true if the bean was deleted successfully or JDBC batch is being used.
*
*
* If there is no current transaction one will be created and committed for
* you automatically.
*
*
* If the Bean does not have a version property (or loaded version property) and
* the bean does not exist then this returns false indicating that nothing was
* deleted. Note that, if JDBC batch mode is used then this always returns true.
*
*
* @see EbeanServer#delete(Object)
*/
public boolean delete() {
return db().delete(this);
}
/**
* Delete a bean permanently without soft delete.
*
* This is used when the bean contains a @SoftDelete
property and we
* want to perform a hard/permanent delete.
*
*
* @see EbeanServer#deletePermanent(Object)
*/
public boolean deletePermanent() {
return db().deletePermanent(this);
}
/**
* Perform an update using this entity against the specified server.
*/
public void update(String server) {
db(server).update(this);
}
/**
* Perform an insert using this entity against the specified server.
*/
public void insert(String server) {
db(server).insert(this);
}
/**
* Perform a delete using this entity against the specified server.
*/
public boolean delete(String server) {
return db(server).delete(this);
}
/**
* Refreshes this entity from the database.
*
* @see EbeanServer#refresh(Object)
*/
public void refresh() {
db().refresh(this);
}
/**
* A concrete implementation of Find.
*
* It should be preferred to use {@link Find} instead of Finder as that can use reflection to determine the class
* literal type of the entity bean.
*
* @param type of the Id property
* @param type of the entity bean
*/
public static class Finder extends Find {
/**
* Create with the type of the entity bean.
*
* {@code
*
* @Entity
* public class Customer extends BaseModel {
*
* public static final Finder find = new Finder(Customer.class);
* ...
*
* }
*
*
* The preferred approach is to instead use Find
as below. This approach is more DRY in that it does
* not require the class literal Customer.class to be passed into the constructor.
*
* {@code
*
* @Entity
* public class Customer extends BaseModel {
*
* public static final Find find = new Find(){};
* ...
*
* }
*/
public Finder(Class type) {
super(null, type);
}
/**
* Create with the type of the entity bean and specific server name.
*/
public Finder(String serverName, Class type) {
super(serverName, type);
}
}
/**
* Helper object for performing queries.
*
*
* Typically a Find instance is defined as a public static field on an entity bean class to provide a
* nice way to write queries.
*
*
Example use:
*
* {@code
*
* @Entity
* public class Customer extends BaseModel {
*
* public static final Find find = new Find(){};
*
* ...
*
* }
*
* This enables you to write code like:
* {@code
*
* Customer customer = Customer.find.byId(42L);
*
* List customers =
* Customer.find
* .select("name, dateOfBirth")
* .findList();
*
* }
*
* Kotlin
* In Kotlin you would typically create Find as a companion object.
* {@code
*
* // kotlin
* companion object : Model.Find() {}
*
* }
* @param
* The Id type. This is most often a {@link Long} but is also often a {@link UUID} or
* {@link String}.
*
* @param
* The entity bean type
*/
public static abstract class Find {
/**
* The entity bean type.
*/
private final Class type;
/**
* The name of the EbeanServer, null for the default server.
*/
private final String serverName;
/**
* Creates a finder for entity of type T
with ID of type I
.
*
* Typically you create Find as a public static field on each entity bean as the example below.
*
*
* Note that Find is an abstract class and hence {}
is required. This is done so
* that the type (class literal) of the entity bean can be derived from the generics parameter.
*
* {@code
*
* @Entity
* public class Customer extends BaseModel {
*
* // Note the trailing {} as Find is an abstract class.
* // We do this so that we can derive the type literal Customer.class
* // via reflection
* public static final Find find = new Find(){};
* ...
*
* }
*
* This enables you to write code like:
* {@code
*
* Customer customer = Customer.find.byId(42L);
*
* List customers =
* Customer.find
* .select("name, email, dateOfBirth")
* .findList();
*
* }
*
* Kotlin
* In Kotlin you would typically create it as a companion object.
*
* {@code
*
* // kotlin
* companion object : Model.Find() {}
*
* }
*/
@SuppressWarnings("unchecked")
public Find() {
this.serverName = null;
this.type = (Class)ClassUtil.getSecondArgumentType(getClass());
}
/**
* Construct passing the class literal type of the entity type.
*/
protected Find(String serverName, Class type) {
this.serverName = serverName;
this.type = type;
}
/**
* Return the underlying 'default' EbeanServer.
*
*
* This provides full access to the API such as explicit transaction demarcation etc.
*
*/
public EbeanServer db() {
return Ebean.getServer(serverName);
}
/**
* Return typically a different EbeanServer to the default.
*
* This is equivilent to {@link Ebean#getServer(String)}
*
* @param server
* The name of the EbeanServer. If this is null then the default EbeanServer is
* returned.
*/
public EbeanServer db(String server) {
return Ebean.getServer(server);
}
/**
* Creates a Finder for the named EbeanServer.
*
*
* Create and return a new Finder for a different server.
*/
public Finder on(String server) {
return new Finder(server, type);
}
/**
* Delete a bean by Id.
*
* Equivalent to {@link EbeanServer#delete(Class, Object)}
*/
public void deleteById(I id) {
db().delete(type, id);
}
/**
* Retrieves all entities of the given type.
*
*
* This is the same as (synonym for) {@link #findList()}
*/
public List all() {
return findList();
}
/**
* Retrieves an entity by ID.
*
*
* Equivalent to {@link EbeanServer#find(Class, Object)}
*/
@Nullable
public T byId(I id) {
return db().find(type, id);
}
/**
* Creates an entity reference for this ID.
*
*
* Equivalent to {@link EbeanServer#getReference(Class, Object)}
*/
public T ref(I id) {
return db().getReference(type, id);
}
/**
* Creates a filter for sorting and filtering lists of entities locally without going back to
* the database.
*
* Equivalent to {@link EbeanServer#filter(Class)}
*/
public Filter filter() {
return db().filter(type);
}
/**
* Creates a query.
*
* Equivalent to {@link EbeanServer#find(Class)}
*/
public Query query() {
return db().find(type);
}
/**
* Creates a query applying the path properties to set the select and fetch clauses.
*
* Equivalent to {@link Query#apply(FetchPath)}
*/
public Query apply(FetchPath fetchPath) {
return db().find(type).apply(fetchPath);
}
/**
* Returns the next identity value.
*
* @see EbeanServer#nextId(Class)
*/
@SuppressWarnings("unchecked")
public I nextId() {
return (I) db().nextId(type);
}
/**
* Executes a query and returns the results as a list of IDs.
*
* Equivalent to {@link Query#findIds()}
*/
public List