Configuración

El fichero de configuración XML contiene parámetros y configuraciones que tienen un efecto crucial en cómo se comporta MyBatis. A alto nivel contiene:

properties

Contiene propiedades externalizables y sustituibles que se pueden configurar en un típico properties de Java o bien puede definirse su contenido directamente mediante subelementos property. Por ejemplo:

<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>

Las propiedades pueden usarse a lo largo del fichero de configuración para sustituir valores que deben configurarse dinámicamente. Por ejemplo:

<dataSource type="POOLED">
  <property name="driver" value="${driver}"/>
  <property name="url" value="${url}"/>
  <property name="username" value="${username}"/>
  <property name="password" value="${password}"/>
</dataSource>

El usuario y password de este ejemplo se reemplazarán por los valores de los elementos de tipo property. El driver y la url se reemplazarán por los valores contenidos en el fichero config.properties. Esto aumenta mucho las posibilidades de configuración.

Las propiedades también pueden pasarse como parámetro al método SqlSessionFactoryBuilder.build(). Por ejemplo:

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, props);

// ... or ...

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, props);

Si una propiedad existe en más de un lugar, MyBatis la carga en este orden:

  • Primero se leen las propiedades especificadas en el elemento XML properties,
  • Posteriormente se cargan las propiedades de recursos de tipo classpath o url del elementos properties, si hubiera una propiedad repetida sería sobrescrita,
  • Y finalmente se leen las propiedades pasadas como parámetro, que en caso de duplicidad sobrescriben las propiedades que se hayan cargado del elemento properties o de recursos/url.

Por tanto las properties más prioritarias son las pasadas como parámetro, seguidas de los atributos tipo classpath/url y finalmente las propiedades especificadas en el elemento properties..

Since the MyBatis 3.4.2, your can specify a default value into placeholder as follow:

<dataSource type="POOLED">
  <!-- ... -->
  <property name="username" value="${username:ut_user}"/> <!-- If 'username' property not present, username become 'ut_user' -->
</dataSource>

This feature is disabled by default. If you specify a default value into placeholder, you should be enable this feature by adding a special property as follow:

<properties resource="org/mybatis/example/config.properties">
  <!-- ... -->
  <property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/> <!-- Enable this feature -->
</properties>

NOTE Also If you are used already the ":" as property key(e.g. db:username) or you are used already the ternary operator of OGNL expression(e.g. ${tableName != null ? tableName : 'global_constants'}) on your sql definition, you should be change the character that separate key and default value by adding a special property as follow:

<properties resource="org/mybatis/example/config.properties">
  <!-- ... -->
  <property name="org.apache.ibatis.parsing.PropertyParser.default-value-separator" value="?:"/> <!-- Change default value of separator -->
</properties>
<dataSource type="POOLED">
  <!-- ... -->
  <property name="username" value="${db:username?:ut_user}"/>
</dataSource>

settings

Son muy importantes para definir cómo se comporta MyBatis en ejecución. La siguiente tabla describe las configuraciones (settings), sus significados y sus valores por defecto.

Configuración Descripción Valores admitidos Valor por defecto
cacheEnabled Habilita o inhabilita globalmente todas las cachés definidas en el mapper true | false true
lazyLoadingEnabled Habilita o inhabilita globalmente la carga diferida (lazy loading). Cuando está activo, todas las relaciones se cargan en modo diferido. Este valor se puede sobrescribir en cada relación usando el atributo fetchType. true | false false
aggressiveLazyLoading When enabled, any method call will load all the lazy properties of the object. Otherwise, each property is loaded on demand (see also lazyLoadTriggerMethods). true | false false (true in ≤3.4.1)
multipleResultSetsEnabled Habilita o inhabilita la obtención de múltiples ResultSets con una sola sentencia (se requiere un driver compatible) true | false true
useColumnLabel Utiliza la etiqueta de columna (label) en lugar del nombre de conlumna. Algunos drivers se comportan distinto en lo que a esto respecta. Consulta la documentación del driver o prueba ambos modos para descubrir cómo funciona tu driver true | false true
useGeneratedKeys Habilita el uso del soporte JDBC para claves autogeneradas. Se requiere un driver compatible. Este parámetro fuerza el uso de las claves autogeneradas si está habilitado. Algunos drivers indican que no son compatibles aunque funcionan correctamente (ej. Derby) true | false False
autoMappingBehavior Especifica cómo deben mapearse de forma automática las columnas a los campos/propiedades. NONE desactiva el mapeo automático. PARTIAL sólo mapea automáticamente los resultados que no contienen result maps anidados en su interior. FULL mapea resultados de cualquier complejidad (contengan anidados o no). NONE, PARTIAL, FULL PARTIAL
autoMappingUnknownColumnBehavior Specify the behavior when detects an unknown column (or unknown property type) of automatic mapping target.
  • NONE: Do nothing
  • WARNING: Output warning log (The log level of 'org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' must be set to WARN)
  • FAILING: Fail mapping (Throw SqlSessionException)
NONE, WARNING, FAILING NONE
defaultExecutorType Configura el ejecutor (executor) por defecto. SIMPLE no hace nada especial. REUSE reúsa prepared statements. BATCH reúsa statements y ejecuta actualizaciones en batch. SIMPLE REUSE BATCH SIMPLE
defaultStatementTimeout Establece el número de segundos que debe esperar el driver la respuesta de la base de datos. Cualquier entero positivo Sin valor (null)
defaultFetchSize Sets the driver a hint as to control fetching size for return results. This parameter value can be override by a query setting. Cualquier entero positivo Sin valor (null)
safeRowBoundsEnabled Habilita el uso de RowBounds en statements anidados. If allow, set the false. true | false False
safeResultHandlerEnabled Habilita el uso de ResultHandler en statements anidados. If allow, set the false. true | false True
mapUnderscoreToCamelCase Mapea automáticamente los nombres clásicos de columnas de base de datos A_COLUMN a nombres clásicos de propiedades Java aColumn. true | false False
localCacheScope MyBatis usa una cache local para evitar dependencias circulares y acelerar ejecuciones repeticas de queries anidadas. Por defecto (SESSION) todas las queries ejecutadas en una sesión se cachean. Si localCacheScope=STATEMENT la sesión local solo se usará durante la ejecución de un statement, no se comparten datos entre distintas llamadas a SqlSession. SESSION | STATEMENT SESSION
jdbcTypeForNull Permite especificar el tipo JDBC que Especifica el tipo JDBC para valores nulos cuando no se ha especificado un tipo concreto para el parámetro. Algunos drivers requieren que se indique el tipo JDBC de la columna pero otros permite valores genéricos como NULL, VARCHAR or OTHER. JdbcType enumeration. Los más comunes son: NULL, VARCHAR and OTHER OTHER
lazyLoadTriggerMethods Permite especificar qué métodos de Object disparan la carga diferida Lista de métodos separados por comas equals,clone,hashCode,toString
defaultScriptingLanguage Permite especificar que lenguaje se usará en el SQL dinámico. Un type alias o una nombre de clase completamente cualificado org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
callSettersOnNulls Permite especificar si se invocarán a los setters y a los métodos put de los mapas si el valor obtenido es null. Te en cuenta que si se activa las propiedades que puedan ser informadas con nulos no pueden ser de tipos primitivos. true | false false
returnInstanceForEmptyRow MyBatis, by default, returns null when all the columns of a returned row are NULL. When this setting is enabled, MyBatis returns an empty instance instead. Note that it is also applied to nested results (i.e. collectioin and association). Since: 3.4.2 true | false false
logPrefix Permite especificar el prefijo que MyBatis añadirá a los nombres de logger. Cualquier cadena No informado
logImpl Permite especificar qué implementación de logging utilizar. Si no está informado la impelmentación se descubrirá automaticamente. SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING No informado
proxyFactory Permite especificar qué herramienta de generación de proxys se usará para crear los objetos con capacidad de carga lazy. CGLIB | JAVASSIST JAVASSIST (MyBatis 3.3 or above)
vfsImpl Specifies VFS implementations Fully qualified class names of custom VFS implementation separated by commas. Not set
useActualParamName Allow referencing statement parameters by their actual names declared in the method signature. To use this feature, your project must be compiled in Java 8 with -parameters option. (Since: 3.4.1) true | false true

A continuación se muestra un ejemplo del elemento settings al completo:

<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
  <setting name="useColumnLabel" value="true"/>
  <setting name="useGeneratedKeys" value="false"/>
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <setting name="defaultStatementTimeout" value="25"/>
  <setting name="defaultFetchSize" value="100"/>
  <setting name="safeRowBoundsEnabled" value="false"/>
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <setting name="localCacheScope" value="SESSION"/>
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

typeAliases

Un type alias es simplemente un alias (un nombre más corto) para un tipo Java. Solo es importante para la configuración XML y existe para reducir la cantidad de texto al teclear nombres de clase cualificados (fully qualified). Por ejemplo:

<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
  <typeAlias alias="Comment" type="domain.blog.Comment"/>
  <typeAlias alias="Post" type="domain.blog.Post"/>
  <typeAlias alias="Section" type="domain.blog.Section"/>
  <typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>

Con esta configuración, puede usarse Blog en lugar de domain.blog.Blog

Tambien puedes indicar un paquete para que MyBatis busque beans de tipo alias. Por ejemplo:

<typeAliases>
  <package name="domain.blog"/>
</typeAliases>

Cada bean encontrado en domain.blog, en caso de que no contenga ninguna anotación, se registrará como alias usando su nombre no cualificado en minúsculas. Es decir, domain.blog.Author se registrará como will be registered as author. Si se encuentra la anotación @Alias se usará su valor como alias. Mira el ejemplo a continuación:

@Alias("author")
public class Author {
    ...
}

Hay muchos type aliases pre construidos. No son sensibles a mayúsculas/minúsculas. Observa los nombres especiales de los tipos primitivos dadas las colisiones de nombres.

Alias Tipo mapeado
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator

typeHandlers

Cuando MyBatis establece el valor de un parámetro de un PreparedStatement u obtiene un valor de un ResultSet, se utiliza un TypeHandler para convertir el valor al tipo Java apropiado. La siguiente tabla recoge los TypeHandlers predefinidos.

NOTE If you use classes provided by JSR-310(Date and Time API), you can use the mybatis-typehandlers-jsr310.

Type Handler Tipos Java Tipos JDBC
BooleanTypeHandler java.lang.Boolean, boolean Cualquiera compatible con BOOLEAN
ByteTypeHandler java.lang.Byte, byte Cualquiera compatible con NUMERIC o BYTE
ShortTypeHandler java.lang.Short, short Cualquiera compatible con NUMERIC o SHORT INTEGER
IntegerTypeHandler java.lang.Integer, int Cualquiera compatible con NUMERIC o INTEGER
LongTypeHandler java.lang.Long, long Cualquiera compatible con NUMERIC o LONG INTEGER
FloatTypeHandler java.lang.Float, float Cualquiera compatible con NUMERIC o FLOAT
DoubleTypeHandler java.lang.Double, double Cualquiera compatible con NUMERIC o DOUBLE
BigDecimalTypeHandler java.math.BigDecimal Cualquiera compatible con NUMERIC o DECIMAL
StringTypeHandler java.lang.String CHAR, VARCHAR
ClobReaderTypeHandler java.io.Reader -
ClobTypeHandler java.lang.String CLOB, LONGVARCHAR
NStringTypeHandler java.lang.String NVARCHAR, NCHAR
NClobTypeHandler java.lang.String NCLOB
BlobInputStreamTypeHandler java.io.InputStream -
ByteArrayTypeHandler byte[] Cualquiera compatible con byte stream
BlobTypeHandler byte[] BLOB, LONGVARBINARY
DateTypeHandler java.util.Date TIMESTAMP
DateOnlyTypeHandler java.util.Date DATE
TimeOnlyTypeHandler java.util.Date TIME
SqlTimestampTypeHandler java.sql.Timestamp TIMESTAMP
SqlDateTypeHandler java.sql.Date DATE
SqlTimeTypeHandler java.sql.Time TIME
ObjectTypeHandler Any OTHER, or unspecified
EnumTypeHandler Enumeration Type VARCHAR Cualquiera compatible con string porque se guarda el código (no el índice).
EnumOrdinalTypeHandler Enumeration Type Cualquiera compatible con NUMERIC o DOUBLE por que se guarda la posición (no el código).

Es posible sobrescribir los TypeHanders o crear TypeHanders personalizados para tratar tipos no soportados o no estándares. Para ello, debes implementar la interfaz org.apache.ibatis.type.TypeHandler o extender la clase de ayuda org.apache.ibatis.type.BaseTypeHandler y opcionalmente mapear el TypeHandler a un tipo JDBC. Por ejemplo:

// ExampleTypeHandler.java
@MappedJdbcTypes(JdbcType.VARCHAR)
public class ExampleTypeHandler extends BaseTypeHandler<String> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
    ps.setString(i, parameter);
  }

  @Override
  public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
    return rs.getString(columnName);
  }

  @Override
  public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    return rs.getString(columnIndex);
  }

  @Override
  public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
    return cs.getString(columnIndex);
  }
}
<!-- mybatis-config.xml -->
<typeHandlers>
  <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>

Al usar este TypeHandler se sobrescribe el TypeHandler existente para los tipos String y los parámetros y resultados VARCHAR. Observa que MyBatis no introspecciona la base de datos para conocer el tipo así que debes especificar que se trata de un VARCHAR en los mapeos de parámetros y resultados para que se use el TypeHandler adecuado. Esto se debe a que MyBatis no conoce nada sobre los tipos de datos hasta que la sentencia ha sido ejecutada.

MyBatis conoce el tipo Java que quieres gestionar introspecionando tipo genérico del TypeHandler, pero puedes modificar este comportamiento de dos maneras.

  • Añadir un atributo javaType al elemento typeHandler (por ejemplo: javaType="String")
  • Añadir una anotación @MappedTypes a tu clase TypeHandler especificando la lista de tipos java a la que asociarlo. Esta anotación será ignorada si se ha especificado también un atributo javaType.

El tipo JDBC asociado se puede especificar de dos maneras:

  • Añadiendo un atributo jdbcType al lemento typeHandler (por ejemplo: jdbcType="VARCHAR").
  • Añadiendo una anotación @MappedJdbcTypes a tu clase TypeHandler especificando la lista de tipos JDBC a la que asociarlo. Esta anotación será ignorada si se ha especificado también un atributo jdbcType.

When deciding which TypeHandler to use in a ResultMap, the Java type is known (from the result type), but the JDBC type is unknown. MyBatis therefore uses the combination javaType=[TheJavaType], jdbcType=null to choose a TypeHandler. This means that using a @MappedJdbcTypes annotation restricts the scope of a TypeHandler and makes it unavailable for use in ResultMaps unless explicity set. To make a TypeHandler available for use in a ResultMap, set includeNullJdbcType=true on the @MappedJdbcTypes annotation. Since Mybatis 3.4.0 however, if a single TypeHandler is registered to handle a Java type, it will be used by default in ResultMaps using this Java type (i.e. even without includeNullJdbcType=true).

Y finalmente puedes hacer que MyBatis busque tus TypeHandlers:

<!-- mybatis-config.xml -->
<typeHandlers>
  <package name="org.mybatis.example"/>
</typeHandlers>

Observa que cuando usas la función de búsqueda los tipos JDBC sólo se pueden especificar usando anotaciones.

Puedes crear un TypeHandler genérico que sea capaz de manejar más de un tipo de clase. Para ello añade un constructor que recibe una clase como parámetro y MyBatis le pasará la clase actual cuando construya el TypeHandler.

//GenericTypeHandler.java
public class GenericTypeHandler<E extends MyObject> extends BaseTypeHandler<E> {

  private Class<E> type;

  public GenericTypeHandler(Class<E> type) {
    if (type == null) throw new IllegalArgumentException("Type argument cannot be null");
    this.type = type;
  }
  ...

EnumTypeHandler y EnumOrdinalTypeHandler son TypeHandlers genéricos. Conoceremos más sobre ellos en la próxima sección.

Handling Enums

Si quires mapear un Enum, debes usar bien un EnumTypeHandler o un EnumOrdinalTypeHandler.

Por ejemplo, digamos que quieres guardar el modo de reondeo que debe usarse con un número determinado que debe redondearse. Por defecto MyBatis usa un EnumTypeHandler para comvertir los valores del Enum a sus nombres.

Observa que el EnumTypeHandler es un handler especial en el sentido de que no maneja una clase específica, como los demás handlers sino cualquier clase que extiende de Enum

Sin embargo, puede que no queramos guardar nombres. Nuestro DBA puede insistir en que usemos un entero en su lugar. Muy sencillo: añade un EnumOrdinalTypeHandler a las sección de typeHandlers de tu fichero de configuración y ahora todos los RoundingMode se mapearán a un entero usando su valor ordinal.

<!-- mybatis-config.xml -->
<typeHandlers>
  <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="java.math.RoundingMode"/>
</typeHandlers>

Pero ¿y si quieres mapear el mismo Enum a un string en un sitio pero a un entero en otro?

El mapeo automático siempre usará EnumOrdinalTypeHandler, así que si queremos usar el clásico EnumTypeHandler, debemos indicarlo establiencidolo esplícitamente su uso en los statements.

Los mappers no se tratarán hasta la sección siguiente asi que si esta es tu primera lectura de la documentación quizá prefieras saltarte esta sección por ahora y volver más tarde).

<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="org.apache.ibatis.submitted.rounding.Mapper">
	<resultMap type="org.apache.ibatis.submitted.rounding.User" id="usermap">
		<id column="id" property="id"/>
		<result column="name" property="name"/>
		<result column="funkyNumber" property="funkyNumber"/>
		<result column="roundingMode" property="roundingMode"/>
	</resultMap>

	<select id="getUser" resultMap="usermap">
		select * from users
	</select>
	<insert id="insert">
	    insert into users (id, name, funkyNumber, roundingMode) values (
	    	#{id}, #{name}, #{funkyNumber}, #{roundingMode}
	    )
	</insert>
	
	<resultMap type="org.apache.ibatis.submitted.rounding.User" id="usermap2">
		<id column="id" property="id"/>
		<result column="name" property="name"/>
		<result column="funkyNumber" property="funkyNumber"/>
		<result column="roundingMode" property="roundingMode" typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>
	</resultMap>
	<select id="getUser2" resultMap="usermap2">
		select * from users2
	</select>
	<insert id="insert2">
	    insert into users2 (id, name, funkyNumber, roundingMode) values (
	    	#{id}, #{name}, #{funkyNumber}, #{roundingMode, typeHandler=org.apache.ibatis.type.EnumTypeHandler}
	    )
	</insert>

</mapper>

Observa que esto nos fuerza a usar un resultMap en lugar de un resultType en nuestros statements tipo select.

objectFactory

Cada vez que MyBatis crea una nueva instancia de un objeto de resultado usa una instancia de ObjectFactory para hacerlo. El ObjectFactory por defecto no hace mucho más que instanciar la clase destino usando su constructor por defecto, o el constructor que se ha parametrizado en su caso. Es posible sobrescribir el comportamiento por defecto creando tu propio ObjectFactory. Por ejemplo:

// ExampleObjectFactory.java
public class ExampleObjectFactory extends DefaultObjectFactory {
  public Object create(Class type) {
    return super.create(type);
  }
  public Object create(Class type, List<Class> constructorArgTypes, List<Object> constructorArgs) {
    return super.create(type, constructorArgTypes, constructorArgs);
  }
  public void setProperties(Properties properties) {
    super.setProperties(properties);
  }
  public <T> boolean isCollection(Class<T> type) {
    return Collection.class.isAssignableFrom(type);
  }
}
<!-- mybatis-config.xml -->
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
  <property name="someProperty" value="100"/>
</objectFactory>

La interfaz ObjectFactory es muy sencilla. Contiene solo dos métodos de creación, uno para el constructor por defecto y otro para el constructor parametrizado. Adicionalmente el método setProperties sirve para configurar el ObjectFactory. Las propiedades definidas en el cuerpo del elemento objectFactory se pasan al método setProperties después de que el ObjectFactory haya sido inicializado.

plugins

MyBatis permite interceptar las llamadas en ciertos puntos de la ejecución de un mapped statement. Por defecto, MyBatis permite incluir plugins que intercepten las llamadas de:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

Los detalles de estos métodos se pueden conocer observando sus firmas y el código fuente de los mismos que está disponible en el sitio de MyBatis. Es recomendable que comprendas el funcionamiento del método que estas sobrescribiendo siempre que vayas a hacer algo más complejo que monitorizar llamadas. Ten en cuenta que si modificas el comportamiento de alguno de estos métodos existe la posibilidad de que rompas el funcionamiento de MyBatis. Estas clases son de bajo nivel y por tanto debes usar los plugins con cuidado.

Utilizar un plugin es muy sencillo para la potencia que ofrecen. Simplemente implementa el interfaz Interceptor y asegúrate de especificar las signaturas que quieres interceptar.

// ExamplePlugin.java
@Intercepts({@Signature(
  type= Executor.class,
  method = "update",
  args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
  public Object intercept(Invocation invocation) throws Throwable {
    return invocation.proceed();
  }
  public Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }
  public void setProperties(Properties properties) {
  }
}
<!-- mybatis-config.xml -->
<plugins>
  <plugin interceptor="org.mybatis.example.ExamplePlugin">
    <property name="someProperty" value="100"/>
  </plugin>
</plugins>

El plugin anterior interceptará cualquier llamada al método “update” en la instancia de Executor, que es un objeto interno que se encarga de la ejecución a bajo nivel de los mapped statements.

NOTA Acerca de sobrescribir la clase Configuration

Además de modificar el comportamiento de MyBatis mediante los plugins, también es posible sobrescribir la clase Configuración por completo. Extiende la clase, sobrescribe sus métodos y pásala como parámetro en la llamada al método SqlSessionFactoryBuilder.build(myConfig). Nuevamente, ten cuenta que esto puede afectar seriamente al funcionamiento de MyBatis así que úsalo con cuidado.

environments

En MyBatis pueden configurarse varios entornos. De esta forma puedes usar tus SQL Maps en distintas bases de datos por muchos motivos. Por ejemplo puede que tengas una configuración distinta para tus entornos de desarrollo, pruebas y producción. O quizá tengas varias bases de datos en producción que comparten el esquema y quieres usar los mismos SQL maps sobre todas ellas. Como ves, hay muchos casos.

Debes recordar un asunto importante. Cuando configures varios entornos, solo será posible usar UNO por cada instancia de SqlSessionFactory.

Por lo tanto, si quieres conectar a dos bases de datos, deberás crear dos instancias de SqlSessionFactory, una para cada cual. Para el caso de tres bases de datos necesitarás tres instancias y así sucesivamente. Es fácil de recordar:

  • Una instancia de SqlSessionFactory por base de datos

Para indicar qué entorno debe utilizarse, debes informar el parámetro opcional correspondiente en la llamada al SqlSessionFactoryBuilder. Existen dos métodos que aceptan el entorno:

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment,properties);

Si se omite el entorno se usará el entorno por defecto:

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader,properties);

El elemento environments contiene la configuración del entorno:

<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>
    <dataSource type="POOLED">
      <property name="driver" value="${driver}"/>
      <property name="url" value="${url}"/>
      <property name="username" value="${username}"/>
      <property name="password" value="${password}"/>
    </dataSource>
  </environment>
</environments>

Observa que las secciones importantes son:

  • El ID del entorno por defecto (ej. default=”development”).
  • El ID de de cada entorno definido (ej. id=”development”).
  • La configuración del TransactionManager (ej. type=”JDBC”)
  • La configuración del DataSource (ej. type=”POOLED”)

El ID del entorno por defecto y de los entornos existentes son auto-explicativos. Puedes nombrarlos como más te guste, tan sólo asegúrate de que el valor por defecto coincide con un entorno existente.

transactionManager

MyBatis incluye dos tipos de TransactionManager (ej. type=”[JDBC|MANAGED]”):

  • JDBC – Este TransactionManager simplemente hace uso del las capacidades de commit y rollback de JDBC. Utiliza la conexión obtenida del DataSource para gestionar la transacción.
  • MANAGED – Este TransactionManager no hace nada. No hace commit ni rollback sobre la conexión. En su lugar, permite que el contenedor gestione el ciclo de vida completo de la transacción (ej. Spring o un servidor de aplicaciones JEE). Por defecto cierra la conexión. Sin embargo, algunos contenedores no esperan que la conexión se cierre y por tanto, si necesitas cambiar este comportamiento, informa la propiedad closeConnection a false. Por ejemplo:
    <transactionManager type="MANAGED">
      <property name="closeConnection" value="false"/>
    </transactionManager>

NOTA Si estás pensando en usar MyBatis con Spring no necesitas configurar ningún TransactionManager porque el módulo de Spring configurará el suyo propio sobrescribiendo cualquier otra configuración previa.

Ninguno de estos TransactionManagers necesita ninguna propiedad. Sin embargo ambos son Type Aliases, es decir, en lugar de usarlos puedes informar el nombre totalmente cualificado o el Type Alias de tu propia implementación del interfaz TransactionFactory:

public interface TransactionFactory {
  void setProperties(Properties props);  
  Transaction newTransaction(Connection conn);
  Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);  
}

Todas las propiedades que configures en el XML se pasarán al método setProperties() tras la instanciación de la clase. Tu implementación debe crear una implementación de Transaction, que a su vez es también un interfaz muy sencillo:

public interface Transaction {
  Connection getConnection() throws SQLException;
  void commit() throws SQLException;
  void rollback() throws SQLException;
  void close() throws SQLException;
  Integer getTimeout() throws SQLException;
}

Con estos dos interfaces puedes personalizar por completo la forma en la que MyBatis gestiona las transacciones.

dataSource

El elemento dataSource sirve para configurar la forma de obtener conexiones JDBC mediante la interfaz DataSource JDBC estándar.

  • La mayoría de las aplicaciones que usen MyBatis configurarán el dataSource como se muestra en el ejemplo. Sin embargo, esta configuración no es obligatoria. Ten en cuenta, aun así, que el dataSource es necesario para utilizar Lazy Loading.

Hay tres tipos de dataSources pre-construidos (ej. type=”????”):

UNPOOLED – Esta implementación de DataSource abre y cierra una conexión JDBC cada vez que se solcita una conexión. Aunque es un poco lento, es una buena elección para aplicaciones que no necesitan la velocidad de tener conexiones abiertas de forma inmediata. Las bases de datos tienen un rendimiento distinto en cuanto al rendimiento que aportan con este tipo de DataSource, para algunas de ellas no es muy importante tener un pool y por tanto esta configuración es apropiada. El DataSource UNPOOLED tiene cinco opciones de configuración:

  • driver – El nombre completamente cualificado de la clase java del driver JDBC (NO de la clase DataSource en el caso de que tu driver incluya una).
  • url – La URL de la instancia de base de datos.
  • username – El usuario de conexión.
  • password – La password de conexión.
  • defaultTransactionIsolationLevel – El nivel de aislamiento por defecto con el que se crearán las conexiones.

Opcionalmente, puedes también pasar propiedades al driver de la base de datos. Para ello prefija las propiedades con “driver.”, por ejemplo:

  • driver.encoding=UTF8

Esto pasaría la propiedad “encoding” con el valor “UTF8” al driver de base datos mediante el método DriverManager.getConnection(url, driverProperties).

POOLED – Esta implementación de DataSource hace usa un pool de conexiones para evitar el tiempo necesario en realizar la conexión y autenticación cada vez que se solicita una nueva instancia de conexión. Este es un enfoque habitual en aplicaciones Web concurrentes para obtener el mejor tiempo de respuesta posible.

Además de las propiedades de (UNPOOLED) hay otras muchas propiedades que se pueden usar para configurar el DataSource POOLED:

  • poolMaximumActiveConnections – Número máximo de conexiónes activas que pueden existir de forma simultánea. Por defecto: 10
  • poolMaximumIdleConnections – Número máximo de conexiones libres que pueden existir de forma simultánea.
  • poolMaximumCheckoutTime – Tiempo máximo que puede permanecer una conexión fuera del pool antes de que sea forzosamente devuelta. Por defecto: 20000ms (20 segundos)
  • poolTimeToWait – Este es un parámetro de bajo nivel que permite escribir un log y reintentar la adquisición de una conexión en caso de que no se haya conseguido la conexión transcurrido un tiempo razonable (esto evita que se produzcan fallos constantes y silenciosos si el pool está mal configurado). Por defecto: 20000ms (20 segundos)
  • poolPingQuery – La query de ping (sondeo) que se envía a la base de datos para verificar que la conexión funciona correctamente y que está lista para aceptar nuevas peticiones de conexión. El valor por defecto es "NO PING QUERY SET", que hará que la mayoría de los drivers de base de datos devuelvan un error con un mensaje de error decente.
  • poolPingEnabled – Habilita o inhabilita la query de ping. Si está habilitada deberías informar también la propiedad poolPingQuery con una sentencia SQL (preferentemente una rápida). Por defecto: false.
  • poolPingConnectionsNotUsedFor – Configura la frecuencia con la que se ejecutará la sentencia poolPingQuery. Normalmente se iguala al timeout de la conexión de base de datos para evitar pings innecesarios. Por defecto: 0 (todas las conexiones se testean continuamente – solo si se ha habilitado poolPingEnabled).

JNDI – Esta implementación de DataSource está pensada para ser usada en contenedores como Spring o los servidores de aplicaciones JEE en los que es posible configurar un DataSource de forma externa y alojarlo en el contexto JNDI. Esta configuración de DataSource requiere solo dos propiedades:

  • initial_context – Propiedad que se usa para realizar el lookup en el InitialContext (initialContext.lookup(initial_context)). Esta propiedad es opcional, si no se informa, se buscará directamente la propiedad data_source.
  • data_source – Es el contexto donde se debe buscar el DataSource. El DataSource se buscará en el contexto resultado de buscar data_source en el InitialContext o si no se ha informado la propiedad se buscará directamente sobre InitialContext.

Al igual que en las otras configuraciones de DataSource. Es posible enviar propiedades directamente al InitialContext prefijando las propiedades con “env.”, por ejemplo:

  • env.encoding=UTF8

Enviará la propiedad “encoding” y el valor “UTF-8” al constructor del InitialContext durante su instanciación.

You can plug any 3rd party DataSource by implementing the interface org.apache.ibatis.datasource.DataSourceFactory:

public interface DataSourceFactory {
  void setProperties(Properties props);
  DataSource getDataSource();
}

The org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory puede extenderse para crear nuevos adaptadores. Por ejemplo, este es el código necesario para integrar C3P0:

import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;
import com.mchange.v2.c3p0.ComboPooledDataSource;
        
public class C3P0DataSourceFactory extends UnpooledDataSourceFactory {

  public C3P0DataSourceFactory() {
    this.dataSource = new ComboPooledDataSource();
  }
}

Para configurarlo, añade una propiedad por cada método al que quieres que llame MyBatis. A continuación se muestra una configuración de ejemplo para conectar con una base de datos PostgresSQL:

<dataSource type="org.myproject.C3P0DataSourceFactory">
  <property name="driver" value="org.postgresql.Driver"/>
  <property name="url" value="jdbc:postgresql:mydb"/>
  <property name="username" value="postgres"/>
  <property name="password" value="root"/>
</dataSource>

databaseIdProvider

MyBatis puede ejeutar sentencias distintas en función del fabricante (vendor) de tu base de datos. El soporte de múltiples bases de datos se basa en el atributo de databaseId de los mapped statements. MyBatis cargará todos los statements que no tengan atributo databaseId attribute o aquellos cuyo databaseId coincida con el valor en curso. Si se encuentra un statement con y sin atributo databaseId el último se descartará. Para activar el soporte de multi vendor añade un databaseIdProvider al fichero mybatis-config.xml file de la siguiente forma:

<databaseIdProvider type="DB_VENDOR" />

La implementación DB_VENDOR del databaseIdProvider establece como databaseId el String devuelto por DatabaseMetaData#getDatabaseProductName(). Como normalmente este string es demasiado largo, y además, distintas versiones del mismo producto devuelven valores similares, puedes traducirlo a un valor más corto añadiendo propiedades de la siguente forma:

<databaseIdProvider type="DB_VENDOR">
  <property name="SQL Server" value="sqlserver"/>
  <property name="DB2" value="db2"/>        
  <property name="Oracle" value="oracle" />
</databaseIdProvider>

Cuando se añaden propiedades, el databaseIdProvider DB_VENDOR devuelve el primer valor que corresponde a la primera clave encontrada en el nombre devuelto por DatabaseMetaData#getDatabaseProductName() o "null" si no se encuentra ninguna. En este caso, si getDatabaseProductName() devuelve "Oracle (DataDirect)" el databaseId se informará con "oracle".

Puedes construir tu propio DatabaseIdProvider implementando la interfaz org.apache.ibatis.mapping.DatabaseIdProvider y registrandolo en el fichero mybatis-config.xml:

public interface DatabaseIdProvider {
  void setProperties(Properties p);
  String getDatabaseId(DataSource dataSource) throws SQLException;
}

mappers

Ahora que se ha configurado el comportamiento de MyBatis con todos los elementos de configuración comentados estamos listos para definir los SQL mapped statements (sentencias SQL mapeadas). Primeramente necesitaremos indicarle a MyBatis dónde encontrarlos. Java no ofrece muchas posibilidades de auto-descubrimiento así que la mejor forma es simplemente decirle a MyBatis donde encontrar los ficheros de mapeo. Puedes utilizar referencias tipo classpath, o tipo path o referencias url completamente cualificadas (incluyendo file:///) . Por ejemplo:

<!-- Using classpath relative resources -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- Using url fully qualified paths -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- Using mapper interface classes -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- Register all interfaces in a package as mappers -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

Esta configuración sólo indica a MyBatis cuáles son los ficheros de mapeo. El resto de la configuración se encuentra dentro de estos ficheros, y eso es de lo que hablaremos en el siguiente apartado.