Getting started guide

Using siena is very easy. You just create your model classes and start to use them. The only configuration is the annotation in your clases and a simple properties file. The model classes must follow some constraints:

  1. The class can extend siena.Model (not mandatory but most pratical). See the examples bellow for more information.
  2. To perform queries easily is strongly recommended to implement an all() method like in the example. Nevertheless this is an optional step.
  3. You can add an @Table annotation. The meaning of this annotation will depend on the underlying siena implementation. This will be the table name in a relational database, the entity name in the Google App Engine datastore... The annotation is just named Table because is an easy to recognize nomenclature.
  4. You can add @Column annotations to the fields. This is very similar to the @Table annotation. The meaning of the annotation value will depend on the underlying siena implementation.
  5. You must mark your primary key fields with an @Id annotation. Some keys can be generated automatically by the application or the database. Manual keys are also allowed. See the example.
  6. Note: in the example the fields are public for simplicity but they can be private or whatever you want.

Model Example

import siena.*;
import static siena.Json.*;

@Table("employees")
public class Employee extends Model {
	
	@Id(Generator.AUTO_INCREMENT)
	public Long id;
	
	@Column("first_name")
	@Max(200) @NotNull
	public String firstName;
	
	@Column("last_name")
	@Max(200) @NotNull
	public String lastName;
	
	public Long age;
	
	@Column("boss") @Index("boss_index")
	public Employee boss;
	
	@Filter("boss")
	public Query<Employee> employees;
	
	@Column("contact_info")
	public Json contactInfo;
	
	@EmbeddedMap
	public static class Contact {
		public String name;
		public List tags;
	}
	
	@Embedded
	public Map contacts;

	@Embedded
	public List otherContacts;
	
	public byte[] photo;

	public static enum ServiceEnum {
		ALPHA,
		BETA,
		DELTA,
		GAMMA,
		EPSILON;
	}

	public ServiceEnum service;
	
	public static Query<Employee> all() {
		return Model.all(Employee.class);
	}

	public static Batch<Employee> batch() {
		return Model.batch(Employee.class);
	}
}

This example shows a class with:

Now you just need to write a simple configuration file. In siena all the classes in the same package are configured with the same PersistenceManager. The configuration file must be called siena.properties and must be placed in the same package than the model classes.

The parameters in this configuration file depend on the siena implementation. The only shared configuration parameter is the implementation parameter. With that parameter you set what siena implementation will be used for the classes in that pacakge. Example:

implementation = siena.jdbc.JdbcPersistenceManager
driver = com.mysql.jdbc.Driver
url = jdbc:mysql://localhost/siena-example
user = root
password = 1234

The example has configured the siena-jdbc implementation. As you can see the siena-jdbc implementation requires some other configuration parameters. You can learn more about each siena implementation in the specific documentation for each implementation.

Now that your model is created and configured you can start using siena.

Querying objects

First of all an example:

List<Employee> employees = Employee.all()
	.filter("firstName", "Mark")
	.order("-lastName")
	.fetch(10);

The all() method is a good starting point for executing queries. That method returns a Query object that is a representation of a query. The Query interface has four main methods: filter(), order(), fetch() and get().

The filter() method

This method puts restrictions to the query. It requires two parameters: the field name (optionally with an operator) and the restricted value. If no opeartor is specified then "=" is assumed. You can use other operators: <, >, <= or >=. You can call filter() several times. The query will only return those objects that match all restrictions. There is no way to specify that the query will return objects that match some restrictions or others. Comparing to SQL the filter() method is like an AND operator in a WHERE clause and there is no support for a OR operator.

The order() method

This is the method you will use for sorting. It requires one parameter that is the name of the field that will be used for sorting. You can concatenate a "-" before the field name for descending sort. You can call this method several times.

The fetch() method

This method will return a list of objects that match the given constraints sorted by the given fields. There are three versions of this method to implement pagination. If you pass no arguments all the objects that match the constraints will be returned. Be careful with that because if you have several objects stored. You can limit the maximum size of returned objects with the first argument. And optionally you can define an offset as second argument.

The get() method

If you just want the first result of the query you can use get(). This method will return null if there query returns no objects.

Important note

It is important to note that nothing is really queried until you call fetch() or get(). The methods all(), filter() and order() just prepare the query but they don't execute it.

Working with single objects

The Siena API is very easy. If your model classes inherith the methods of the siena.Model class. These methods will let you insert, update, delete or load single objects. If you don't want to use inheritance because you can't or you want to follow the POJO philosophy you can use the methods of the PersistenceManager class instead of using the methods of the siena.Model class. For example:

Employee e = getSomeEmployee();
// retrieve the configured PersistenceManager:
PersistenceManager pm = PersistenceManagerFactory.getPersistenceManager(Employee.class);
// the following two lines are equivalent:
pm.update(e); // you will use it if you don't extend siena.Model
e.update(); // if Employee extends siena.Model

Note: all the examples bellow use the siena.Model methods but you will find equivalent methods in the PersistenceManager class.

Loading an object

If you have the primary keys of an object and you want to load all its fields you just need to create an empty object, put the primary key field values and call get().

Employee e = new Employee();
e.id = 123; // we know the primary keys
e.get(); // this loads the object
System.out.println(e.firstName);

If no object is found with those primary keys then a SienaException will be thrown.

If you don't want to handle exceptions you can use the Query interface. Example:

Employee e = Employee.all().filter("id", 123).get();
System.out.println(e.firstName);

This way you will get null instead of an exception if there is no such object.

You can even create your own static method:

public static Employee get(long id) {
	return Employee.all().filter("id", 123).get();
}

Inserting a new object

Inserting an object is very simple. Just load the fields with the appropiate values and then call insert(). If your object has some generated primary keys you will be able to get them just after inserting the object.

Employee e = new Employee();
e.firstName = "John";
e.lastName = "Smith";
e.insert();
System.out.println(e.id); // the generated key is available

Updating an object

The update() method lets you update an object in the persistence storage.

Employee e = Employee.get(123);
e.firstName = "Mark";
e.update();

Deleting an object

Finally you can delete an object using the delete() method.

Employee e = Employee.get(123);
e.delete();

The delete() method only needs the primary keys to be loaded. So if you know them you don't even need to execute a query to previously load the object.

Working with relationships

In order to create a one-to-many relationship you just need to create a reference from the child class to the parent class. Example:

public class Pet extends Model {
	
	@Column("owner")
	public Person owner; // each pet has an owner
	
	// more fields
	
}

It is strongly recommended to use the @Column annotation when declaring relationships if you are using sinea-jdbc.

To fetch all the pets of a given person you can just query filtering by that field.

Person p = somePerson();
Query<Pet> pets = Pet.all().filter("owner", p);
List<Pet> somePets = pets.fetch(10);

Or you can also create an automatic-query in the Person class.

public class Person extends Model {
	
	@Filter("owner")
	public Query<Pet> pets; // this is called an "automatic-query"
	
	// more fields
	
}
Person p = somePerson();
List<Pet> somePets = p.pets.fetch(10);

As you can see the @Filter annotation tells which field must be used to query against the Pet class.

Embedded objects

In the first example at this page you can see that a Json field has been used to store complex data structures into one field. Well, there is another way to store complex data strctures into one field: using embedded objects. Suppose you have a web page whose users have "profile images". Each image has a filename, a title and a counter that counts how many times the image has been displayed.

public class User extends Model {
	@Embedded
	public List<Image> profileImages;

	// more fields
}

You just need to put the @Embedded annotation to the field that will store the embedded data. This field can be an object, a java.util.List or a java.util.Map. However the sotored objects must be of a class properly annotated. Let's see how:

@EmbeddedMap
public class Image {
	public String filename;
	public String title;
	public int views;
}

You have annotate your embeddec classes either with @EmbedMap or @EmbedList. The embedded object will be serialized into JSON when inserted in the database. An example of how the profileImages field could be serialized:

[{"title": "Example 1", "views": 2, "filename": "1.jpg"},
	{"title": "Example 2", "views": 20, "filename": "2.jpg"}]

When using @EmbedList the object will be serialized into a JSON list. In this case you must annotate the fields using @At. This is an example:

@EmbeddedList
public class Image {
	@At(0) public String filename;
	@At(1) public String title;
	@At(2) public int views;
}

The fields must be in order as well. This is how the object would be serialized:

[["1.jpg", "Example 1", 2],
	["2.jpg", "Example 2", 20]]

The JSON result is shorter but harder to understand.

The "embedded objects" feature is very powerful because you can nest embedded objects into other embedded objects. So for example the Image class could have other objects nested or other lists or maps inside it. This feature also has de avantaje of knowing the structure of the data at compile time.