# mybatis框架 ## Mybatis中主要的类及其生命周期 ### SqlSessionFactoryBuilder 对于SqlSessionFactoryBuilder,其推荐的生命周期是创建之后立即销毁,不必被保留;其理想的作用域是方法的作用域。 > 可以将SqlSessionFactoryBuilder进行保留,并用其创建多个SqlSessionFactory实例。但是,为了保证XML解析后资源被正常的释放,应该在将其创建之后立刻丢弃 ### SqlSessionFactory 对于SqlSessionFactory实例,其理想的作用域和生命周期都应该是整个应用的执行期间。在应用运行期间,都会通过该工厂实例来获取SqlSession对象。 > SqlSessionFactory实例在创建完成之后,都不应该被修改或者是被重新创建。 ### SqlSession 对于SqlSession对象来说,其生命周期和作用域应该是方法级别或者是请求级别。SqlSession对象并不是线程安全的,无法在多线程环境之下被共享。因而,无法将其作为Servlet或是HttpSession的属性或是静态属性,否则会产生非预期的并发安全问题。 推荐情况下,SqlSession应该在接受到http请求时被创建,并且在请求返回httpResponse时被销毁。 并且,SqlSession必须要被保证正确的关闭。 ```java try(SqlSession sqlSession=sqlSessionFactory.openSession()) { // operations on sqlSession } ``` ### Mapper实例 Mapper实例是通过SqlSession实例来获取的,故而,Mapper实例最大的生命周期和作用域和SqlSession。Mapper最推荐的生命周期和作用域是方法级别的,在方法中,获取Mapper并使用后即可丢弃,无需在方法外保存Mapper对象。 虽然Mapper也可以和SqlSession一样在request范围内有效,但是会发现保留太多Mapper级别的资源而不销毁会很快的产生失控。 Mapper实例和SqlSession实例不同,并不需要被显式的销毁。 ## Mybatis中的配置文件 Mybatis中配置文件主要有properties和setting两部分。 ### properties 对于那些可外部化、可替换的属性,可以通过java的.properties文件进行配置,或者通过<properties>标签中配置的子属性来进行传入,具体如下: ```xml ``` 此时,在整个xml配置文件中,都可以使用${username}和${password}变量或者从config.properties文件中导入的变量。 在向SqlSessionFactoryBuilder.build方法中传入propertis属性时,优先级如下: ```java /* * 首先,会读入xml配置文件中properties标签中的变量内容 * 然后,会读入properties标签resource或者url属性指定的properties属性 * 最后,会读入作为参数传入的props属性 * 按上述顺序读入属性,后读入的属性会覆盖前面读入的同名属性 */ SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, props); // ... or ... SqlSessionFactory factory = new SqlSessionFactoryBuilder.build(reader, environment, props); ``` ### settings 配置settings属性可以修改mybatis运行时的行为。 ### typeAliases typeAliases标签中可以定义全限定类名的别名,在定义typeAliases之后,xml文件中可以使用别名来代替全限定类名 ```xml ``` 如下是java常用类型的别名:
Aias Mapped Type
_byte byte
_int int
byte Byte
string String
long Long
### typeHandler 每当mybatis向PreparedStatement设置值或者从ResultSet中获取值时,typeHandler都会使用合适的方法来将ResultSet中的属性传递给java类对象或者从Java类对象中获取属性并将其传递给PreparedStatement的sql语句。 mybatis内置了很多typeHandler,用户可以实现自己的TypeHandler,通过实现TypeHandler接口或者继承BaseTypeHandler类。 ```java /** * 该方法会覆盖默认的针对String类型和varchar类型的typeHandler **/ // ExampleTypeHandler.java @MappedJdbcTypes(JdbcType.VARCHAR) public class ExampleTypeHandler extends BaseTypeHandler { @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); } } ``` ```xml ``` mybatis会通过泛型参数来获知其typeHandler想要处理的javaType,但是可以通过两种方法来覆盖该种行为: - 在typeHandler元素中添加javaType属性(例如,javaType="String") - 通过@MappedTypes注解指定该typeHandler想要对应的javaType列表(如果javaType属性在typeHandler元素中被指定,那么该注解内容将会被忽略) 同样的,jdbcType也可以通过两种方式来指定: - 在typeHandler元素中添加jdbcType属性(例如,jdbcType="VARCHAR") - 为typeHandler类添加@MappedJdbcTypes注解,并且在jdbcType属性被指定时该注解被忽略 当决定在ResultMap中使用typeHandler时,java类型已知但是jdbc类型未知。因此ResultMap采用javaType=[javaType],jdbcType=null的组合来选择typeHandler。为了使typeHandler在ResultMap中能够被使用,需要在@MappedJdbcTypes加入includeNullJdbcType=true。 > 如果对于某一个java类型,只有一个typeHandler被注册,那么该typeHandler会自动被使用,即使includeNullJdbcType=true并没有被设置。 ### EnumTypeHandler 对于枚举类型的处理,可以使用EnumTypeHandler和EnumOridinalTypeHandler。 > 默认情况下,mybatis会使用EnumTypeHandler来处理枚举类型,会将枚举类型的转化成其名字的字符串。 > 可以强制将EnumOridinalTypeHandler指定给sql语句的typeHandler属性,此时会将枚举类型转换成其对应的数字值。 ```xml ``` 可以在不同的地方使用不同的枚举类型处理器,将其映射为整数类型或字符串类型。只需在mapper使用非默认类型时显示指定即可。 ```xml insert into users (id, name, funkyNumber, roundingMode) values ( #{id}, #{name}, #{funkyNumber}, #{roundingMode} ) insert into users2 (id, name, funkyNumber, roundingMode) values ( #{id}, #{name}, #{funkyNumber}, #{roundingMode, typeHandler=org.apache.ibatis.type.EnumTypeHandler} ) ``` ### ObjectFactory mybatis会使用ObjectFactory来创建返回对象。ObjectFactory仅仅会调用默认构造函数或者参数化的构造函数来创建返回对象。 ### environment Mybatis可以设置复数个环境,但是对于每一个SqlSessionFactory对象,只能够选择一个环境。为了指定构建的环境,可以将其以参数的方式传递给SqlSessionFactoryBuilder.build。如果environment参数被省略,那么默认的环境将会被采用。 环境的配置格式如下: ```xml ``` #### transactionManager 当在spring中使用mybatis框架时,无需在environment中指定transactionManager,Spring会用自己的transactionManager来覆盖environment中的定义。 #### datasource 在datasource标签中,通过标准jdbc接口来定义。datasource可以分为如下三个类型:POOLED|UNPOOLED|JNDI - UNPOOLED:对于每次请求,都会都会打开一个新的连接,并且在请求结束之后关闭该链接。 - POOLED:会在连接池中池化链接,可以避免每次请求都会创建链接和身份认证的开销。在高并发场景下,池化数据源的响应时间要由于未池化的数据源。 - JNDI:JNDI数据源通常和特定容器一起使用,例如EJB服务器,该类服务器会集中或者在外部配置该数据源。 #### mappers 通过mappers属性可以向mybatis注册mapper.xml文件。 ```xml ``` # Mybatis Mapper ## select 通常来说,数据库操作中select的频率远远大于update、insert、delete的频率。 select元素有如下属性: - id: 命名空间中唯一的标识符,用来引用该sql声明 - parameterType: 传递参数的全限定类名或者alias,该属性是可选的,mybatis会根据根据实际传递的参数来计算应该使用的typeHandler - resultType:方法预期返回类型的全类名 > 如果方法的返回类型是集合类型,那么resultType为集合中包含元素的类型,而不是集合本身的类型。 - resultMap:对于外部resultMap的命名引用 > 对于resultType和resultMap,应该只使用其中的一个,同一条select语句中不应该既包含resultType又包含resultMap - flushCache:如果该属性设置为true,在该Statement被调用时,会导致本地缓存和二级缓存都被刷新。默认情况下,flushCache被设置为false。 - useCache:如果该属性设置为true,会导致该Statement的查询结果被缓存在二级缓存中。默认情况下,useCache属性为true。 > mybatis缓存结构: > - 一级缓存: mybatis一级缓存是针对SqlSession的缓存,如果SqlSession查询数据会将第一次查询到的数据存放在Map缓冲区中。如果后续SqlSession没有对数据进行添加、修改、删除等操作,那么缓存将会失效。 > 默认情况下,若未开启事务,那么每条语句都可以看作一个事务,多条select语句之间都会刷新缓 存,一级缓存不会起作用。 > - 二级缓存:mybatis中二级缓存是针对Mapper级别的。二级缓存针对mapper.xml中所有Statement的作用域。二级缓存可以在多个SqlSession之间进行共享。 > 在执行sql查询之前,如果执行了插入或者删除操作,那么二级缓存会失效。 > 二级缓存需要手动配置,settings标签中cacheEnabled默认是true,只需要在需开启缓存的mapper.xml中加入cache标签即可开启二级缓存 - timeout:drvier会等待数据库返回查询结果的超时时间,如果超过该timeout上线,那么会抛出异常。默认情况下,该timeout是未设置的,取决于底层的driver ## update、insert、delete 修改语句通常有如下属性: - id - parameterType - flushCache - timeout - statementType: STATEMENT/PREPARED/CALLABLE其中之一,默认值为PREPARED > statementType通常可以设置为如下值之一: > - STATEMENT:mybatis会使用Statement > - PREPARED:mybatis会使用PreparedStatement > - CALLABLE:mybatis会使用CallableStatement - useGeneratedKeys:当useGeneratedKeys被设置为true时,会调用JDBC中的getGeneratedKeys方法从数据库内部获取自动生成的key。默认情况下该值为false > 该属性仅针对insert和update语句 - keyProperty:后跟随字段或者字段列表,对于想要数据库内部自动生成的字段,mybatis会通过getGeneratedKeys方法返回的值来设置该字段的内容;对于想要自定义字段自动生成(如随机生成)的字段,mybatis会通过insert元素selectKey子元素中的值来设置 ```xml insert into Author (username,password,email,bio) values (#{username},#{password},#{email},#{bio}) select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1 insert into Author (id, username, password, email,bio, favourite_section) values (#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR}) ``` ### selectKey 对于selectKey子元素,其通常具有如下属性: - keyProperty:该属性指定selectKey生成的结果应该被设置到insert语句的哪个地方 - resultType:selectKey语句执行结果的返回类型 - order:可以是“BEFORE"或者”AFTER",如果被设置为“BEFORE",那么会先执行selectKey,然后将产生结果设置到keyProperty,最后才会执行insert语句 ## sql sql标签通常被用来定义一些可以复用的sql片段,sql片段可以接受一个参数。 sql标签定义的sql片段可以被include标签进行引用,并且include标签的refid属性对应sql标签的id属性。 > 如果一个sql片段嵌套了另一个sql片段,那么在sql语句include外层sql片段时,可以同时为内层和外层sql片段的变量进行赋值操作 ```xml ${prefix}Table from ``` ## parameter - 简单的参数映射 ```xml ``` - 向参数传递复杂对象,例如User类型对象,那么将会从对象中获取id、username、password等属性并传递给参数 ```xml insert into users (id, username, password) values (#{id}, #{username}, #{password}) ``` ## mybatis ${} 对于mybatis,可以通过#{}来设置PreparedStatement的占位参数,但是,当想要动态设置sql statement中的元数据(如表名称、字段名称)时,可以通过${}来插入一个未修改的字符串进行拼串。 ```java // 未使用${} @Select("select * from user where id = #{id}") User findById(@Param("id") long id); @Select("select * from user where name = #{name}") User findByName(@Param("name") String name); @Select("select * from user where email = #{email}") User findByEmail(@Param("email") String email); // 使用${}之后 @Select("select * from user where ${column} = #{value}") User findByColumn(@Param("column") String column, @Param("value") String value); ``` ## resultMap - 对于简单的statement,其映射不需要用到resultMap ```xml ``` - 对于POJO,当select的返回列名和POJO类的属性名相同时,会自动生成resultMap来将column和POJO对象属性关联到一起 ```xml ``` - 自定义外部的resultMap来映射到User类 ```xml ``` - 高级结果集映射 ```xml ``` ### resultMap的元素和属性 - resultMap中可嵌套如下元素: - constructor:在通过构造方法实例化类时,将select语句返回的result注入到构造方法中 - idArg:标识为id的参数,将result标识为id可以提高整体的性能 - arg:将result注入到constructor中 - id:标识为id的result,将result标识为id可以提高整体性能 - result:select语句返回的result,将被注入到返回POJO类型的field - association:复杂关联,返回results中的部分results将会被包装到这种关联中 - 关联被嵌套在resultMap中,关联本身可以是resultMap,或者可以引用一个外部resultMap - collection:复杂类型的集合 - 集合被嵌套在resultMap中,其本身可以是一个resultMap,或者其可以引用要给外部的resultMap - discrimination:鉴别器,可以通过结果值来判断使用哪个resultMap - case:条件选项,当值为xx时使用特定的resultMap - case同样是一个resultMap,case可以是resultMap本身,也可以引用其他的resultMap - resultMap元素可包含如下属性: - id:在命名空间内,可以通过该id引用该resultMap - type:java类的全限定类名或typeAlias - autoMapping:为resultMap开启或关闭自动映射 ### id和result元素 id和result元素是resultMap的基础,id和result都将一个column字段值映射到java类型的field域。 > id和result标签的区别是,当result被标识为id时,该result将会被看作对象的标识符,在比较对象时id result将会被使用。这样可以提高整体性能,尤其是在缓存或是嵌套resultMap时。 - id和result元素的属性: - property:column映射到的java对象的field - column:数据库表的列名 - javaType:java类型的全限定类名或者typeAlias,通常映射到java bean时mybatis可以推断java类型,但是当映射到HashMap时,需要显式指定javaType - jdbcType - typeHandler ### constructor元素 当想要构建不可变对象时,可以通过constructor属性来向构造方法中注入result构建对象。 ```java public class User { //... public User(Integer id, String username, int age) { //... } //... } ``` ```xml ``` - constructor元素的属性: - column:数据库表列名 - javaType - jdbcType - typeHandler - resultMap - select