MyBatis简介(二)

一、四大核心组件:SqlSessionFactoryBuilder,SqlSessionFactory,SqlSession,SQL Mapper;
二、组件生命周期
三、详解MyBatis配置:properties ,settings,typeAliases


从(一)的介绍可知,MyBatis有如下优点:
(1)不屏蔽SQL,这样我们就可以对SQL进行优化和改造,从而提供系统性能;
(2)提供强大、灵活的映射机制,动态SQL;
(3)提供Mapper的接口编程:接口+XML就能创建映射器,简化开发;

1.四大核心组件
MyBatis简介(二)
(1)SqlSessionFactoryBuilder(构造器):根据XML配置或java代码生成
SqlSessionFactory,采用的是分步构建Builder模式;SqlSessionFactoryBuilder提供了Configuration类作为引导;每个基于MyBatis的应用都以一个SqlSessionFactory的实例为中心,且SqlSessionFactory唯一的作用就是生成SqlSession,因此会采用单例模式;
MyBatis中的XML文件有两种,基础配置文件(最基本的上下文参数和运行环境)和映射文件(映射关系、SQL、参数等);
使用XML构建SqlSessionFactory:配置在XML,便于日后修改与维护,推荐;

//基础配置文件:mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration   PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <typeAliases>
  	<!-- 别名,role代表对应类Role,在MyBatis上下文就可以使用别名去代替全限定名了-->
      <typeAlias alias="role" type="cn.infocore.mybatis.pojo.Role"/>
  </typeAliases>
  <!-- 数据库环境 -->
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/> <!--事务管理器,采用的是JDBC管理器方式-->
      <dataSource type="POOLED"><!--POOLED代表采用MyBatis内部提供的连接池方式-->
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/xx_db"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
      </dataSource>
    </environment>
  </environments>
  <!-- 映射文件:映射器 -->
  <mappers>
    <mapper resource="cn/infocore/mybatis/mapper/RoleMapper.xml"/>
    <mapper class="cn.infocore.mybatis.mapper.RoleMapper2"/> 
  </mappers>
</configuration>
String resource="mybatis-config.xml";
InputStream ins=Resource.getResourceAsStream(resource);
SqlSessionFactory factory=new SqlSessionactoryBuilder.build(ins);

–使用代码生成SqlSessionFactory:一般不推荐使用,除非特殊需求,如需要配置加密的数据库用户名和密码;

//数据库连接池信息
PooledDataSource dataSource = new PooledDataSource();
dataSource.setDriver("com.mysql.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setUrl("jdbc:mysql://localhost:3306/chapter3");
dataSource.setDefaultAutoCommit(false);
//采用MyBatis的JDBC事务方式
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
//创建Configuration对象
Configuration configuration = new Configuration(environment);
//注册一个MyBatis上下文别名
configuration.getTypeAliasRegistry().registerAlias("role", Role.class);
//加入一个映射器
configuration.addMapper(RoleMapper.class);
configuration.addMapper(RoleMapper2.class);
//使用SqlSessionFactoryBuilder构建SqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
return sqlSessionFactory; 	

(2)SqlSessionFactory(工厂接口):使用工厂模式生成SqlSession;SqlSessionFactory有两个实现类:DefaultSqlSession(单线程环境)和SqlSessionManager(多线程);
创建SqlSession:SqlSession是一个门面接口,真正起作用的是Executor;

SqlSession session=sqlSessionFactory.openSession();
//session.commit();
//session.rollback();
//session.close();

(3)SqlSession(会话):MyBatis的核心接口对象,发送SQL并返回结果,获取Mapper的接口,掌控数据库事务;作用类似于一个Connextion对象,代表着一个连接资源的启用;
(4)SQL Mapper(映射器):Java接口+XML(注解)构成,需给出对应的SQL和映射规则,发送SQL(将POJO数据插入到数据库)并返回结构(将结果映射为一个POJO),配置缓存,提供动态SQL;注意,接口本身是不能直接运行,但MyBatis会运用动态代理技术为这个接口生成一个代理对象,由代理对象去处理相关逻辑,而这一切是MyBatis自动完成的;
–实现方式一:XML文件形式

//-----------接口
public interface RoleMapper {
	public Role getRole(@Param("id") Integer id);
}
//----------mybatis-config.xml加入
<mapper resource="cn/infocore/mybatis/mapper/RoleMapper.xml"/>
//-----------RoleMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.infocore.mybatis.mapper.RoleMapper">  <!--对应接口-->
	<select id="getRole" parameterType="long" resultType="role">
		<!--id是对应接口方法名,parameterType参数类型,resultType返回类型,对应配置文件的别名-->
		select id, role_name as roleName, note from t_role where id = #{id}
	</select>
</mapper>

只要SQL返回的列名与POJO属性名对应起来(命名一样),MyBatis默认会自动映射;
–实现方式二:注解,直接在接口上注入

public interface RoleMapper {
	@Select("select t_id, t_name , t_note from t_role where t_id = #{id}")
	public Role getRole(@Param("id") Integer id);
}

如果同时配置,方式一会覆盖方式二;推荐方式一;

----SqlSession发送SQL:命名空间加SQL ID组合,定位一条SQL;

Role role=(Role)session.selectOne("cn.infocore.mybatis.mapper.RoleMapper.getRole",1);

如果在MyBatis中只有一个id为getRole的SQL可以简写:

Role role=(Role)session.selectOne("getRole",1);

----用Mapper接口发送SQL

RoleMapper mapper=session.getMapper(RoleMapper.class);
Role role3=mapper.getRole(1);

对比:推荐使用Mapper接口发送SQL,因为使用接口可以消除SqlSession带来的功能性代码,提高可读性;且使用接口的方式会有IDE校验和报错,而SqlSession的定位是一个字符串,只有运行时才知道对不对;

2.生命周期
(1)SqlSessionFactoryBuilder:SqlSessionFactoryBuilder是用户创建SqlSessionFactory,因此只存在与创建SqlSessionFactory的方法中;
(2)SqlSessionFactory:可被认为是一个数据库连接池,作用是创建SqlSession接口对象,因此SqlSessionFactory存在于整个MyBatis应用中,等于MyBatis的生命周期,不建议存在多个,因为很容易导致数据库连接资源被消耗光;
(3)SqlSession:可被认为是一个数据库连接(Connection对象),生命周期是一个业务请求中,处理完,关闭连接close,再归还给SqlSessionFactory;
(4)Mapper:由SqlSession创建,所以生命周期最多和SqlSession一样;

3.详解MyBatis配置

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration   PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <properties /><!-- 属性 -->
  <settings /><!-- 设置 -->
  <typeAliases /><!-- 别名 -->
  <environments> <!-- 配置环境 -->
    <environment> <!-- 环境变量 -->
      <transactionManager /> <!-- 事务管理器 -->
      <dataSource /> <!-- 数据源 -->
    </environment>
  </environments>
  <databaseIdProvider /><!-- 数据库厂商标识 -->
  <mappers /> <!-- 映射器 -->
</configuration>

(1)properties :给系统配置一些运行参数,使用方式有三种:
–property子元素:适用于一处定义,多处引用${database.driver};

<properties>
    <propertyname="database.driver" value="com.mysql.jdbc.Driver"></property>
</properties>

–使用properties文件:多处引用${database.driver}

<properties resource="jdbc.properties"/>

–程序代码:适用于需要特殊处理的,如保存的用户名密码需要在创建SqlSessionactory前先解密;

InputStream ins=Resource.getResourceAsStream("jdbc.properties");
Properties prop=new Properties();
props.load(ins);
String password=props.getProperty("database.password");
props.put("database.password",CodeUtils.decode(password));//解密
InputStream ins2=Resource.getResourceAsStream(resource);
SqlSessionFactory session=new SqlSessionFactoryBuilder.build(ins2,props);//覆盖

总结:最优选择程序代码,其次是properties文件,最后是property子元素,如都配置,前者会覆盖后者;
建议使用properties文件,因为比较简单且维护方便,但对于一些特殊要求的场景,那就参考程序代码;
(2)settings:主要MyBatis底层的一些配置,大部分情况下默认即可,先介绍一些常用规则:

<settings>
	<!--所有映射器中配置缓存的全局开关-->
	<setting name="cacheEnabled" value="true"/>
	<!--延迟加载的全局开关-->
	<setting name="lazyLoadingEnabled" value="true"/>
	<!--是否允许单一语句返回多结果集-->
	<setting name="mutipleResultSetsEnabled" value="true"/>
	<!--使用列标签代替列名-->
	<setting name="useColumnLable" value="true"/>
	<!--允许JDBC支持自动生成组件-->
	<setting name="useGeneratedKeys" value="false"/>
	<!--指定MyBatis应如何自动映射列到字段或属性:NONE表示取消自动映射;PARTIAL只会自动映射,没有定义嵌套结果集和映射结果集;FULL会自动映射任意复杂的结果集-->
	<setting name="autoMappingBehavior" value="PARTIAL"/>
	<!--指定自动映射当中未知列或未知属性类型的行为,默认不处理,只有当日志级别是WARN级别及以下才会显示相关日志,取值:NONE,WARNING,FAILING-->
	<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
	<!--默认执行器,SIMPLE是普通,REUSE是重用预处理语句,BATCH是重用语句并执行批量更新-->
	<setting name="defaultExecutorType" value="SIMPLE"/>
	<!--设置超时时间:驱动等待数据库响应的秒数-->
	<setting name="defaultStatementTimeout" value="25"/>
	<!--设置数据库驱动程序默认返回的条数限制-->
	<setting name="defaultFetchSize" value="100"/>
	<!--是否允许在嵌套语句中使用分页,false表示允许-->
	<setting name="safeRowBoundsEnabled" value="false"/>
	<!--是否开启驼峰命名规则映射,即从数据库列名A_Column到POJO属性aColumn-->
	<setting name="mapUnderscoreToCamelCase" value="false"/>
	<!--MyBatis利用本地缓存机制防止循环引用和加速重复嵌套查询,默认SESSION表示缓存一个会话中执行的所有查询;
	STATEMENT表示本地会话金庸在语句执行上,对相同SqlSession的不通调用将不会共享数据-->
	<setting name="localCacheScope" value="SESSION"/>
	<!--当没有为参数提供特定的JDBC类型时,为空值指定JDBC类型,多数情况下使用一般类型即可,如NULL、VARCHAR、OTHER-->
	<setting name="jdbcTypeForNull" value="OTHER"/>
	<!--指定哪个对象的方法触发一次延迟加载,可覆盖cacheEnabled全局-->
	<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

(3)typeAliases别名:在类的全限定名称很长且需要大量使用时,允许定义一个简写来代表这个类,就是别名;分系统定义别名和自定义别名;别名不区分大小写;
MyBatis初始化时,系统自动初始化了一些别名,如下:

public TypeAliasRegistry() {
    //支持数据:别名[]
    registerAlias("string", String.class);

    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    registerAlias("short", Short.class);
    registerAlias("int", Integer.class);
    registerAlias("integer", Integer.class);
    registerAlias("double", Double.class);
    registerAlias("float", Float.class);
    registerAlias("boolean", Boolean.class);

    registerAlias("byte[]", Byte[].class);
    registerAlias("long[]", Long[].class);
    registerAlias("short[]", Short[].class);
    registerAlias("int[]", Integer[].class);
    registerAlias("integer[]", Integer[].class);
    registerAlias("double[]", Double[].class);
    registerAlias("float[]", Float[].class);
    registerAlias("boolean[]", Boolean[].class);

    registerAlias("_byte", byte.class);
    registerAlias("_long", long.class);
    registerAlias("_short", short.class);
    registerAlias("_int", int.class);
    registerAlias("_integer", int.class);
    registerAlias("_double", double.class);
    registerAlias("_float", float.class);
    registerAlias("_boolean", boolean.class);

    registerAlias("_byte[]", byte[].class);
    registerAlias("_long[]", long[].class);
    registerAlias("_short[]", short[].class);
    registerAlias("_int[]", int[].class);
    registerAlias("_integer[]", int[].class);
    registerAlias("_double[]", double[].class);
    registerAlias("_float[]", float[].class);
    registerAlias("_boolean[]", boolean[].class);

    registerAlias("date", Date.class);
    registerAlias("decimal", BigDecimal.class);
    registerAlias("bigdecimal", BigDecimal.class);
    registerAlias("biginteger", BigInteger.class);
    registerAlias("object", Object.class);

    registerAlias("date[]", Date[].class);
    registerAlias("decimal[]", BigDecimal[].class);
    registerAlias("bigdecimal[]", BigDecimal[].class);
    registerAlias("biginteger[]", BigInteger[].class);
    registerAlias("object[]", Object[].class);
   //以下不支持数组
    registerAlias("map", Map.class);
    registerAlias("hashmap", HashMap.class);
    registerAlias("list", List.class);
    registerAlias("arraylist", ArrayList.class);
    registerAlias("collection", Collection.class);
    registerAlias("iterator", Iterator.class);

    registerAlias("ResultSet", ResultSet.class);
  }
public Configuration() {
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

    typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
    typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
    typeAliasRegistry.registerAlias("LRU", LruCache.class);
    typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
    typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

    typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

    typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
    typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

    typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
    typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
    typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
    typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
    typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
    typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
    typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

    typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
    typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

    languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
    languageRegistry.register(RawLanguageDriver.class);
  }

以上就是系统定义的别名,我们使用时,要注意不要重名;自定义别名针对自己定义的对象,可以使用TypeAliasRegistry的registerAlias方法注册,也可以用配置文件或扫描方式;单个注册很简单,之前的代码里有提到过,主要说一下批量注册:

<typeAliases>
      <package name="cn.infocore.mybatis.pojo"/>
</typeAliases>

表示MyBatis将扫描这个包里的类,将其第一个字母变小写作为其别名,不过同时扫描多个包时,可能会出现重名的情况,这个时候就需要使用不同的@Alias(“xxx”)来区分;

@Alias("xxx1")
public Class xxx{}