Tutorials     About     RSS
Tech and Media Labs

Butterfly Persistence Annotation Based Object Mapping

Jakob Jenkov
Last update: 2021-06-30

It is possible to map classes to tables and columns using annotations. This is handy if you only need to modify the automatically guessed mapping a little bit.

Unfortunately annotation based mapping has a drawback. You cannot have more than one mapping for the same class using annotation based mapping alone. Each annotation is only allowed once in the code for a given class by the Java compiler.

It is still possible though, to create two different mappings based on automatic mapping and annotations and then just modify them programmatically into the final different mappings you need.

Here is a quick example to prepare you for how annotation based mapping looks:

@AClassMapping(mappingMode="manual", tableName="persons")
public class Employee {

   @AGetterMapping(column="ID", databaseGenerated=true)
   public long getEmployeeIdentification() {...}

   @ASetterMapping(column="ID")
   public void   setEmployeeIdentitification(long id) {...}
}

Annotation Types

There are two types of annotations available:

  1. A class annotation
  2. Annotations for getters and setters.

These annotations are called AClassMapping, AGetterMapping, and ASetterMapping. Each annotation will be described separately in the following sections.

AClassMapping

The class annotation (AClassMapping) is used ontop of your class name for your POJO classes. Here is an example:

@AClassMapping(mappingMode = modify, tableName = "someWeirdTableName")
public class Employee {
  //members, getters and setters are ommitted here...
}

The class annotation definition looks like this:

package com.jenkov.db.itf.mapping;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AClassMapping {
    String mappingMode() default "modify";  //manual / modify
    String tableName()   default "";
}

The class annotation AClassMapping has two named elements:

  • mappingMode
  • tableName

The mappingMode element can take one of two values: "modify" or "manual". The default value is "modify". If you set the mappingMode to "modify" then Butterfly Persistence will first use automatic mapping to guess as much as it can, and then look at the annotations and modify the class mapping accordingly. That way it is only necessary to provide annotations for mappings which the automatic mapping cannot guess. If you set the mappingMode to "manual" then no automatic mapping will be done. Only the mappings provided in the annotations will be used. The mapper will still try to determine the primary key from the database meta data though.

The tableName element can be set to the name of the table the class is to be mapped to, if it cannot be guessed by the automatic mapping mechanism.

Here are a few examples of AClassMapping annotations:


//mappingMode defaults to "modify"
@AClassMapping(tableName="persons")
public class Employee { ... }


@AClassMapping(mappingMode="manual", tableName="persons")
public class Employee {

   @AGetterMapping(column="ID", databaseGenerated=true)
   public long getEmployeeIdentification() {...}

   @ASetterMapping(column="ID")
   public void   setEmployeeIdentitification(long id) {...}
}

Notice how the second class definition uses manual mapping, so the getter and setter must be fully mapped to columns in the database. This example does so using getter and setter annotations.

AGetterMapping

The AGetterMapping annotation is used to map a getter to a column in a table. Butterfly Persistence uses the getters of an object to extract the values to be inserted or updated in the database, or to extract the primary key value of the matching record to delete. In other words, getters are used by Butterfly Persistence only when updating the database (insert, update and delete). You don't have to map a getter to the database just because you have mapped the corresponding setter method. If you are mapping a setter to a column that only exists in a query, then just set the column name on the setter, and ignore the getter.

Here is the AGetterMapping annotation definition:


package com.jenkov.db.itf.mapping;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AGetterMapping {
    String  columnName()          default "";
    String  columnType()          default "";    // number, string,
                                                 // date, binary
    boolean includeInWrites()     default true;
    boolean databaseGenerated()   default false;
}

As you can see the AGetterMapping annotation can contain four different named elements:

  • columnName
  • columnType
  • includeInWrites
  • databaseGenerated

The columnName element can be set to the name of the column in the table, if manual mapping is used, or if the automatic mapping cannot guess it.

The columnType can be set to one of four values: "number", "string", "date", "binary". You only need to set the columnType element if you are using manual mapping and are using a column type in the database that does not match the return value of the getter. For instance when mapping a Java boolean to a number type as Oracle users often do. If you use automatic mapping the columnType is auto-detected.

The includeInWrites element can be set to either true or false. By default it is true meaning Butterfly Persistence will include the getter in inserts and updates. If set to false Butterfly Persistence will ignore this getter when updating the record matching the object. Note: If you are mapping a setter to a column that exists only in a query, not in the table this object is mapped to, it is often easier just not to map the getter to any column. If not mapped it will also be ignored by Butterfly Persistence in inserts and updates. However, if you use automatic mapping and the getter does match a column in the database, but you just never want it updated, set the includeInWrites to false.

The databaseGenerated element can be set to either true or false. If you set it to true Butterfly Persistence will assume that this value is generated by the database and should not be updated. If you have a database generated (e.g auto-incremented) primary key that is mapped to a getter method, you will have to mark the getter method as database generated, to avoid having Butterly Persistence try to set that value in the database. By default databaseGenerated is set to false.

Here is an example of an AGetterMapping annotation (in bold):

@AClassMapping(mappingMode="manual", tableName="persons")
public class Employee {

   @AGetterMapping(column="ID", databaseGenerated=true)
   public long getEmployeeIdentification() {...}

   @ASetterMapping(column="ID")
   public void   setEmployeeIdentitification(long id) {...}
}

ASetterMapping

The ASetterMapping annotation is used to map setters of classes to columns in database tables or SQL queries. Setters are used by Butterfly Persistence to read columns from queries into the fields of the corresponding objects. Butterfly Persistence calls the setters passing the corresponding column values as parameters. In other words, setters are only used by Butterfly Persistence when reading objects. Not when writing them.

The ASetterMapping annotation definition looks like this:

package com.jenkov.mrpersister.itf.mapping;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ASetterMapping {
    String  columnName()          default "";
    String  columnType()          default ""; // number, string,
                                              // date, binary
}

The ASetterMapping annotation has two named elements:

  • columnName
  • columnType

The columnName element can be set to the name of the column in the table, if manual mapping is used, or if the automatic mapping cannot guess it.

The columnType can be set to one of four values: "number", "string", "date", "binary". You only need to set the columnType element if you are using manual mapping and are using a column type in the database that does not match the return value of the getter. For instance when mapping a Java boolean to a number type as Oracle users often do. If you use automatic mapping the columnType is auto-detected.

Here is an example of an ASetterMapping annotation (in bold):

@AClassMapping(mappingMode="manual", tableName="persons")
public class Employee {

   @AGetterMapping(column="ID", databaseGenerated=true)
   public long getEmployeeIdentification() {...}

   @ASetterMapping(column="ID")
   public void   setEmployeeIdentitification(long id) {...}
}

Jakob Jenkov

Featured Videos





















Sponsored Ads

Maildroppa - Smart Email Marketing Solution
Close TOC

All Trails

Trail TOC

Page TOC

Previous

Next