Active Record活跃记录
Active Record Pattern 活跃记录模式
Active Record is a design pattern first proposed by Martin Fowler. According to Martin Fowler, an active record object represents a row in a database table. Contrary to J2EE's Value Object pattern which is an object having only data fields but no behavior, and to the popular DAO pattern which only has data access behavior but no data, the Active Record pattern describes a rich object which has both data fields and data access behavior.
活跃记录是Martin Fowler首先推荐的一种设计模式,根据他的想法,一个活跃记录对应着一个数据库表的一行。相反,J2EE的一个值对象模式是一个只包含数据字段没有方法的对象,流行的DAO模式之包含数据访问方法但没有数据,活跃记录模式描述了一个同时包含数据字段和数据访问方法的富对象。
Scooter Framework's ActiveRecord class is an implementation of Active Record pattern. All domain objects, if they want to take advantage of functionalities of ActiveRecord, must become a subclass of this class.
Scooter框架的活跃记录类是一个活跃记录模式的实现。所有的域对象,如果它们想利用ActiveRecord的功能,必须先成为这个ActiveRecord类的子类。
For example, a Post class which represents a blog post record in database, can be implemented as follows:
比如说,一个Post类对应着数据库里一个博客的帖记录,应该像下面这样实现:
public class Post extends ActiveRecord { }
Even though this class only has one code line (omitting import and package statement lines), it is already very powerful as it inherits many methods from its super class. It also has knowledge of posts table meta data information such as name and properties of all columns including primary key column(s).
虽然这个类只包含一行代码(省略import和package等状态行),它已经非常强大,因为它从它的父类继承了很多方法。它也知道post表的元数据信息,比如所有列的名字和属性包括主键列。
By default, the Post class in singular form above maps to a table named posts in plural form in database. This can be changed by the use.plural.table.name property in the database.poperties file. There are some other properties in that file related to table naming conventions. For example, if names of all your tables start with a prefix of "CRM_", you simply use the global.table.naming.prefix property.
CRUD Made Easy
CRUD is an abbreviations of four common operations of data acess: create, read, update and delete. CRUD is easy with Scooter Framework's ActiveRecord.
Create
Create a record in database:
ActiveRecord post = new Post(); post.setData("title", "Happy Java Programming"); post.setData("body", "Java programming is fun."); post.create();
The last line above is equivalent to the following sql statement:
insert into posts (title, body) values ('Happy Java Programming', 'Java programming is fun.');
It will create a new record in a posts table.
Read
For a single ActiveRecord instance, it can refresh itself with the latest data in database:
ActiveRecord post = ...; post.reload();
The last line above is equivalent to the following sql statement, assuming 8 is the primary key value of the record:
select * from posts where id = 8;
Update
Update a record in database:
ActiveRecord post = postHome.find("title=Happy Java Programming"); post.setData("title", "Scooter Rocks!"); post.update();
The last line above is equivalent to the following sql statement:
update posts set title='Happy Java Programming', body='Java programming is fun.' where id=8;
It will update the title of the record with id 8--assuming 8 is the value of the primary key, in the posts table.
The update() method will list all columns of the posts table in the update statement. Since we only edited the title column, we can use updateChanged():
ActiveRecord post = postHome.find("title=Happy Java Programming"); post.setData("title", "Scooter Rocks!"); post.updateChanged();
The last line above is equivalent to the following sql statement which uses only those columns whose data are changed:
update posts set title='Happy Java Programming' where id=8;
Scooter Framework's ActiveRecord is smart enough to track which fields are changed and only use those fields in the update sql statement.
Delete
Delte a record in database:
ActiveRecord post = postHome.find("title=Happy Java Programming"); post.delete();
The last line above is equivalent to the following sql statement:
delete from posts where id=8;
ActiveRecord Home Instance
In Scooter Framework, each domain object has a single instance called home instance which acts as a gateway to a database table. This is an implementation of Martin Fowler's Table Data Gateway pattern. Each home instance provides more methods for data retrieval and muniplation in its domain. It also provides meta data for the domain.
Obtain a home instance:
ActiveRecord postHome = ActiveRecordUtil.getHomeInstance(Post.class);
Retrieve
ActiveRecord uses overloaded find() methods to retrieve a record:
//returns the one record we just created above. ActiveRecord post = postHome.find("title=Happy Java Programming");
The above code is equivalent to the following sql statement:
select * from posts where title='Happy Java Programming';
ActiveRecord uses overloaded findAll() methods to retrieve a list of records:
//returns all records satisfying the conditions required List posts = postHome.findAll("title=Happy Java Programming", "conditions_sql: id in (1, 2, 3);order_by: last_name desc");
The above code is equivalent to the following sql statement:
select * from posts where title='Happy Java Programming' and id in (1, 2, 3) order by last_name desc;
Dynamic Retrieval
You can simulate Ruby On Rails' dynamic finder by using ActiveRecord's findBy() and findAllBy() methods.
//The class class Employee extends ActiveRecord {} //retrieve the employee whose first name is John, last name is Doe ActiveRecord john = employeeHome.findBy("firstName_and_lastName", {"John", "Doe"}); //retrieve the employee whose first name is John, last name is Doe and age is 29. ActiveRecord john = employeeHome.findBy("firstName_and_lastName_and_age", {"John", "Doe", new Integer(29)}); //retrieve all employees who live in LA List employees = employeeHome.findAllBy("city", {"LA"});
Back to SQL
ActiveRecord is SQL friendly. You can drop back to SQL if you rely on complicated or historical sql queries. ActiveRecord provides findBySQL(), deleteBySQL(), updateBySQL() mehods. If the sql statement is in external file sql.properties, you can use the corresponding findBySQLKey(), deleteBySQLKey(), updateBySQLKey() mehods.
//retrieve all employees who live in LA List employees = employeeHome.findBySQL("select * from employees where city = 'LA'");
Some Tips
Specifying Table Name Directly
You can set the corresponding table name of an ActiveRecord class directly as follows:
public class Post extends ActiveRecord { public String getTableName() { return "blogs"; } }
Using Non-database Fields
ActiveRecord implicitly pulls fields of a model from database. Therefore, you do not need to list database table fields in the class. However, what if you want to have some non-database fields in the model only? This kind of fields is called extraField in ActiveRecord. You can deal with them as follows:
public class User extends ActiveRecord { protected void declaresExtraFields() { //declare two extra fields setExtraFields("nickName, formerName"); } } //Then you can use them as you use any other fields ActiveRecord david = new User(); david.setData("nickName", "Dave"); System.out.println(david.getField("nickName"));
Using Protected Fields
Protected fields are those table columns that you do not want users to change in an application. They can only be changed through database tools. For example, you may declare username and password as protected fields. Once they are entered in database, you would not allow users to change them.
public class User extends ActiveRecord { protected void declaresProtectedFields() { //declare two protected fields setProtectedFields("username, password"); } } //Then operations like clearAndSetData() and setData() will have no effects on //the protected fields ActiveRecord david = new User(); david.setData("username", "IJustChangedYou"); System.out.println(david.getField("username"));//should still print out unchanged database record.