大纲
-
1、MyBatis中的重要对象
-
2、MyBatis中的重要注解
-
3、MyBatis源码分析
1、MyBatis重要对象
MyBatis完成一次数据库操作需要经过的步骤,如下:
1、加载配置文件 2、获取SqlSessionFactoryBuiler对象 3、通过SqlSessionFactoryBuiler和配置文件流来获取SqlSessionFactory对象 4、利用SqlSessionFactory对象来打开一个SqlSession 5、通过SqlSession来获得对应的Mapper对象 6、通过Mapper对象调用对应接口来查询数据库 从这些步骤我们可以看到,MyBatic完成一次数据库操作主要有4大核心对象:
SqlSessionFactoryBuiler,SqlSessionFactory,SqlSession、Mapper。
-
SqlSessionFactoryBuiler
SqlSessionFactoryBuilder的唯一作用就是用来创建SqlSessionFactory,创建完成之后就不会用到它了,
所以SqlSessionFactoryBuiler生命周期极短。
SqlSessionFactoryBuiler中提供了9个方法,返回的都是SqlSessionFactory对象。
SqlSessionFactoryBuiler内使用到了建造者模式.
-
SqlSessionFactory
SqlSessionFactory是一个接口,默认的实现类,主要是用来生成SqlSession对象,而SqlSession对象是需要不断被创建的,所以SqlSessionFactory是全局都存在的,也没有必要重复创建,所以这是一个单例对象。
SqlSessionFactory看名字就可以很容易想到,用到的是工厂设计模式。
SqlSessionFactory只有两个实现类:DefaultSqlSessionFactory和SqlSessionManager。
- SqlSession
SqlSession是用来操作xml文件中我们写好的sql语句,每次操作数据库我们都需要一个SqlSession对象,SqlSession是用来和数据库中的事务进行对接的,所以SqlSession里面是包含了事务隔离级别等信息的。
SqlSession实例是线程不安全的,故最佳的请求范围是请求(request)或者方法(method)。
SqlSession也是一个接口,有两个实现类:DefaultSqlSession和SqlSessionManager。
-
Mapper
Mapper是一个接口,没有任何实现类。主要作用就是用来映射Sql语句的接口,映射器的接口实例从SqlSession对象中获取,所以说Mapper实例作用域是和SqlSession相同或者更小。
Mapper实例的作用范围最好是保持在方法范围,否则会难以管理。
Mapper接口的名称要和对应sql语句的xml文件同名,Mapper接口中定义的方法名称对应了xml文件中的语句id.
- SqlSessionFactoryBuilder
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
build方法中会调用生成一个XMLConfigBuilder对象:
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
该对象中调用parser.parse()返回一个Configuration对象。
- 四大核心对象生命周期
SqlSessionFactoryBuiler只需要在创建SqlSessionFactory对象的时候使用,创建完成之后即可被丢弃。
SqlSessionFactory全局唯一,是一个单例对象,但是需要全局存在。
SqlSession一般对应了一个request,
Mapper一般控制在方法内
1、MyBatis四大内置对象
- ParameterHandler:处理SQL的参数对象
- ResultSetHandler:处理SQL的返回结果集
- StatementHandler:数据库的处理对象,用于执行SQL语句
- Executor:MyBatis的执行器,用于执行增删改查操作
2、MyBatis中的重要注解
- @Select
作用:标记查询语句。
@Select用于标记查询语句。该注解可以在接口方法上使用,也可以在XML文件中使用。使用@Select注解时,需要在注解中指定SQL语句。
示例:
@Select("SELECT * FROM users WHERE id = #{id}")
User getUserById(@Param("id") Long id);
- @Insert
作用:标记插入语句。
@Insert用于标记插入语句。该注解可以在接口方法上使用,也可以在XML文件中使用。使用@Insert注解时,需要在注解中指定SQL语句。
示例:
@Insert("INSERT INTO users(name, age) VALUES(#{name}, #{age})")
int addUser(User user);
- @Update
作用:标记更新语句。
@Update用于标记更新语句。该注解可以在接口方法上使用,也可以在XML文件中使用。使用@Update注解时,需要在注解中指定SQL语句。
示例:
@Update("UPDATE users SET name = #{name}, age = #{age} WHERE id = #{id}")
int updateUser(User user);
- @Delete
作用:标记删除语句。
@Delete用于标记删除语句。该注解可以在接口方法上使用,也可以在XML文件中使用。使用@Delete注解时,需要在注解中指定SQL语句。
示例:
@Delete("DELETE FROM users WHERE id = #{id}")
int deleteUserById(@Param("id") Long id);
- @Results
作用:用于指定多个@Result注解。
@Results用于标记结果集映射,该注解可以用于接口方法或XML文件中,通常与@Select注解一起使用。使用@Results注解时,需要指定映射规则。
示例:
@Select("SELECT * FROM users WHERE id = #{id}")
@Results(id = "userResultMap", value = {
@Result(property = "id", column = "id"),
@Result(property = "name", column = "name"),
@Result(property = "age", column = "age")
})
User getUserById(@Param("id") Long id);
-- @Result
作用:用于指定查询结果集的映射关系。
@Result用于标记单个属性与结果集中的列之间的映射关系。该注解可以用于接口方法或XML文件中,
通常与@Results注解一起使用。使用@Result注解时,需要指定映射规则。
- @ResultMap
作用:用于指定查询结果集的映射关系。
@ResultMap用于标记结果集映射规则。该注解可以用于接口方法或XML文件中,通常与@Select注解一起使用。使用@ResultMap注解时,需要指定映射规则。
示例:
@Select("SELECT * FROM users WHERE id = #{id}")
@ResultMap("userResultMap")
User getUserById(@Param("id") Long id);
- @Options
作用:用于指定插入语句的选项。
@Options用于指定一些可选的配置项。该注解可以用于接口方法或XML文件中,通常与@Insert、@Update、@Delete等注解一起使用。使用@Options注解时,可以指定一些可选的配置项。
示例:
@Insert("INSERT into users(name, age) VALUES(#{name}, #{age})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertUser(User user);
- @SelectKey
作用:用于指定查询语句的主键生成方式。
@SelectKey用于在执行INSERT语句后获取自动生成的主键值。该注解可以用于接口方法或XML文件中,通常与@Insert注解一起使用。
使用@SelectKey注解时,需要指定生成主键的SQL语句和将主键值赋给Java对象的哪个属性。
示例:
@Insert("INSERT INTO users(name, age) VALUES(#{name}, #{age})")
@SelectKey(statement = "SELECT LAST_INSERT_ID()", keyProperty = "id", before = false, resultType = Long.class)
int insertUser(User user);
- @Param
作用:用于指定方法参数名称。
@Param用于为SQL语句中的参数指定参数名称。该注解可以用于接口方法或XML文件中,通常与@Select、@Insert、@Update、@Delete等注解一起使用。使用@Param注解时,需要指定参数名称。
示例:
@Select("SELECT * FROM users WHERE name = #{name} AND age = #{age}")
List<User> getUsersByNameAndAge(@Param("name") String name, @Param("age") Integer age);
- @One
作用:用于指定一对一关联关系。
@One用于在一对一关联查询中指定查询结果的映射方式。该注解可以用于XML文件中,通常与和标签一起使用。使用@One注解时,需要指定查询结果映射的Java对象类型和查询结果映射的属性。
<resultMap id="userResultMap" type="User">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
<association property="department" resultMap="departmentResultMap"/>
</resultMap>
<resultMap id="departmentResultMap" type="Department">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="description" property="description"/>
<one property="manager" resultMap="userResultMap"/>
/resultMap>
上述代码中,@One注解用于指定查询结果的映射方式,这里使用了嵌套的标签实现了一对一关联查询。在departmentResultMap中,使用@One注解指定了查询结果映射的Java对象类型为User,查询结果映射的属性为manager,resultMap参数指定了查询结果映射的结果集映射规则为userResultMap。
除了使用@One注解之外,还可以使用@Many注解来指定一对多关联查询的映射方式。总之,@One注解是MyBatis中用于在一对一关联查询中指定查询结果的映射方式的注解之一,可以方便地实现一对一关联查询的结果映射。
- @Many
作用:用于指定一对多关联关系。
@Many用于在一对多关联查询中指定查询结果的映射方式。该注解可以用于XML文件中,通常与和标签一起使用。使用@Many注解时,需要指定查询结果映射的Java对象类型和查询结果映射的属性。
示例:
<resultMap id="departmentResultMap" type="Department">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="description" property="description"/>
<collection property="members" ofType="User" resultMap="userResultMap"/>
</resultMap>
<resultMap id="userResultMap" type="User">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
</resultMap>
上述代码中,@Many注解用于指定查询结果的映射方式,这里使用了嵌套的
除了使用@Many注解之外,还可以使用@One注解来指定一对一关联查询的映射方式。总之,@Many注解是MyBatis中用于在一对多关联查询中指定查询结果的映射方式的注解之一,可以方便地实现一对多关联查询的结果映射。
-
@ResultType
作用:用于指定查询结果集的类型。
@ResultType用于指定查询结果的类型。该注解可以用于接口方法或XML文件中,通常与@Select、@Insert、@Update、@Delete等注解一起使用。使用@ResultType注解时,需要指定查询结果的类型。
示例:
@Select("SELECT name, age FROM users WHERE id = #{id}") @ResultType(User.class) User getUserById(Long id);
-
@TypeDiscriminator
作用:用于指定类型鉴别器,用于根据查询结果集的不同类型映射到不同的Java对象。
@TypeDiscriminator用于在自动映射时指定不同子类型的映射方式。该注解可以用于XML文件中,通常与和标签一起使用。使用@TypeDiscriminator注解时,需要指定类型列的名称和不同子类型的映射方式。
-
@ConstructorArgs
作用:用于指定Java对象的构造方法参数。
@ConstructorArgs用于指定查询结果映射到Java对象时使用的构造函数和构造函数参数。该注解可以用于XML文件中,通常与标签一起使用。使用@ConstructorArgs注解时,需要指定构造函数参数的映射关系。
示例:
<resultMap id="userResultMap" type="User"> <id column="id" property="id"/> <constructor> <arg column="name" javaType="String"/> <arg column="age" javaType="int"/> </constructor> </resultMap>
-
@Arg
作用:用于指定Java对象的构造方法参数。
@Arg用于指定查询结果映射到Java对象时构造函数或工厂方法的参数映射关系。该注解可以用于接口方法或XML文件中,通常与@Select、@Insert、@Update、@Delete等注解一起使用。使用@Arg注解时,需要指定参数的映射关系。
示例:
@Select("SELECT name, age FROM users WHERE id = #{id}") User getUserById(@Arg("name") String name, @Arg("age") int age);
-
@Discriminator
作用:用于指定类型鉴别器的查询结果。
@Discriminator用于在自动映射时指定不同子类型的映射方式。该注解可以用于接口方法或XML文件中,通常与@Select、@Insert、@Update、@Delete等注解一起使用。使用@Discriminator注解时,需要指定类型列的名称和不同子类型的映射方式。
示例:
@Select("SELECT * FROM vehicle WHERE type = #{type}") @Discriminator(column = "type", javaType = String.class, cases = { @Case(value = "car", type = Car.class), @Case(value = "truck", type = Truck.class), @Case(value = "bus", type = Bus.class) }) List<Vehicle> getVehiclesByType(String type);
-
@CacheNamespace
作用:用于指定缓存的命名空间。
@CacheNamespace用于指定Mapper接口中的查询结果是否进行缓存。该注解可以用于Mapper接口上,用于指定Mapper接口中所有方法默认的缓存配置。使用@CacheNamespace注解时,需要指定缓存配置的属性。
示例:
@CacheNamespace( implementation = MyBatisRedisCache.class, eviction = MyBatisRedisCache.Eviction.LRU, flushInterval = 60000, size = 10000, readWrite = true, blocking = true ) public interface UserMapper { @Select("SELECT * FROM users WHERE id = #{id}") User getUserById(Long id); // ... }
-
@Flush
作用:用于在插入、更新或删除操作之后自动清空缓存。
@Flush是用于在Mapper接口中指定在执行方法前或方法后刷新缓存。该注解可以用于Mapper接口方法上,通常与@Select、@Insert、@Update、@Delete等注解一起使用。使用@Flush注解时,需要指定刷新缓存的时机。
示例:
@Select("SELECT * FROM users WHERE id = #{id}")
@Flush(flushCache = FetchType.AFTER)
User getUserById(Long id);
-
@MappedJdbcTypes
作用:用于指定Java对象属性与数据库列的映射关系。
@MappedJdbcTypes用于将Java类型映射到JDBC类型。该注解可以用于JavaBean属性或ResultMap中,用于指定Java类型对应的JDBC类型。使用@MappedJdbcTypes注解时,需要指定Java类型和对应的JDBC类型。
示例:
public class User { private Long id; @MappedJdbcTypes(JdbcType.VARCHAR) private String name; private Integer age; // ... }
-
@MappedTypes
作用:用于指定Java对象与数据库类型的映射关系。
@MappedTypes用于将Java类型映射到JDBC类型。该注解可以用于JavaBean属性或ResultMap中,用于指定Java类型对应的JDBC类型。使用@MappedTypes注解时,需要指定Java类型。
示例:
@MappedTypes(User.class) public interface UserMapper { @Select("SELECT * FROM users WHERE id = #{id}") User getUserById(Long id); // ... }
-
@SelectProvider
作用:用于指定动态生成SQL语句的提供者。
@SelectProvider是用于在Mapper接口中动态生成查询SQL语句。该注解可以用于Mapper接口方法上,用于指定一个提供SQL语句的Provider类。使用@SelectProvider注解时,需要指定Provider类和Provider方法。
示例:
@SelectProvider(type = UserSqlProvider.class, method = "getUserByIdSql") User getUserById(Long id);
-
@InsertProvider
作用:用于指定动态生成SQL语句的提供者。
@InsertProvider用于在Mapper接口中动态生成插入SQL语句。该注解可以用于Mapper接口方法上,用于指定一个提供SQL语句的Provider类。使用@InsertProvider注解时,需要指定Provider类和Provider方法。
示例:
@InsertProvider(type = UserSqlProvider.class, method = "insertUserSql") int insertUser(User user);
-
@UpdateProvider
作用:用于指定动态生成SQL语句的提供者。
@UpdateProvider用于在Mapper接口中动态生成更新SQL语句。该注解可以用于Mapper接口方法上,用于指定一个提供SQL语句的Provider类。使用@UpdateProvider注解时,需要指定Provider类和Provider方法。
示例:
@UpdateProvider(type = UserSqlProvider.class, method = "updateUserSql")
int updateUser(User user);
-
@DeleteProvider
作用:用于指定动态生成SQL语句的提供者。
@DeleteProvider用于在Mapper接口中动态生成删除SQL语句。该注解可以用于Mapper接口方法上,用于指定一个提供SQL语句的Provider类。使用@DeleteProvider注解时,需要指定Provider类和Provider方法。
示例:
@DeleteProvider(type = UserSqlProvider.class, method = "deleteUserSql") int deleteUser(Long id);
3、MyBatis源码分析
1、源码分析流程图
2、源码分析流程
- 读取resources获取对应的InputStream
public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
if (in == null) {
throw new IOException("Could not find resource " + resource);
} else {
return in;
}
}
- 使用SqlSessionFactoryBuilder获取SqlSessionFactory
SqlSessionFactoryBuilder 通过XMLConfigBuilder 去解析我们传入的mybatis的配置文件
// 第一步
public SqlSessionFactory build(InputStream inputStream) {
return this.build((InputStream)inputStream, (String)null, (Properties)null);
}
// 第二步
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException var13) {
}
}
return var5;
}
- XMLConfigBuilder 部分源码
// 第一步
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
// 第二步
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
this.localReflectorFactory = new DefaultReflectorFactory();
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
返回parser,调用 build(parser.parse())这个方法去解析配置文件内容,我们去看看parse()方法源码。
public Configuration parse() {
if (this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
// 防止多次调用
this.parsed = true;
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
}
}
解析配置:parseConfiguration(parseConfiguration)
private void parseConfiguration(XNode root) {
try {
this.propertiesElement(root.evalNode("properties"));
Properties settings = this.settingsAsProperties(root.evalNode("settings"));
this.loadCustomVfs(settings);
this.loadCustomLogImpl(settings);
this.typeAliasesElement(root.evalNode("typeAliases"));
this.pluginElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
this.settingsElement(settings);
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
this.typeHandlerElement(root.evalNode("typeHandlers"));
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}
通过以上源码,我们就能看出,在mybatis的配置文件中:
configuration节点为根节点。
在configuration节点之下,我们可以配置10个子节点, 分别为:properties、typeAliases、plugins、objectFactory、objectWrapperFactory、settings、environments、databaseIdProvider、typeHandlers、mappers。
解析配置文件完成了之后,都会装配到configuration
Configuration作用:mybatis核心的配置文件内容 ,使用xml转换bean
- 解析配置文件中配置的mappers: 进入这个方法源码看下如何解析的:mapperElement(root.evalNode("mappers"));
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
Iterator var2 = parent.getChildren().iterator();
while(true) {
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String resource;
if ("package".equals(child.getName())) {
resource = child.getStringAttribute("name");
this.configuration.addMappers(resource);
} else {
resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
XMLMapperBuilder mapperParser;
InputStream inputStream;
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
inputStream = Resources.getResourceAsStream(resource);
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
inputStream = Resources.getUrlAsStream(url);
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
mapperParser.parse();
} else {
if (resource != null || url != null || mapperClass == null) {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
Class<?> mapperInterface = Resources.classForName(mapperClass);
this.configuration.addMapper(mapperInterface);
}
}
}
return;
}
}
}
mapperElement方法就是将解析到的mapper,调用addMappers方法添加mapper。
public void addMappers(String packageName) {
this.mapperRegistry.addMappers(packageName);
}
- MapperRegistry 类部分代码
MapperRegistry类里面封装了一个map集合,用来存放mappers接口
public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (this.hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
this.knownMappers.put(type, new MapperProxyFactory(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
this.knownMappers.remove(type);
}
}
}
}
}
通过上面的源码分析我们可以知道,使用map集合来装接口:再用configuration来接受配置文件所有信息。
最后,回到build方法:
-- SqlSessionFactoryBuilder的build方法
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
-- DefaultSqlSessionFactory 类部分代码
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
public SqlSession openSession() {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}
}
通过SqlSessionFactoryBuilder类拿到SqlSessionFactory对象,SqlSessionFactory对象调用openSession方法即可拿到SqlSession对象,然后可以通过SqlSession拿到Configuration对象,从而可以拿到装在的配置和mapper。
- 操作Mapper接口
-- 获取mapper接口
TsCompareDetailLogMapper tsCompareDetailLogMapper = sqlSession.getMapper(TsCompareDetailLogMapper.class);
-- 最后底层是调用MapperRegistry类的getMapper方法获取对应mapper接口
public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
public MapperRegistry(Configuration config) {
this.config = config;
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
}
-- MapperProxyFactory通过Java动态代理拿到mapper接口。
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return this.mapperInterface;
}
public Map<Method, MapperMethodInvoker> getMethodCache() {
return this.methodCache;
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
}
通过上述源码分析,我们知道了Mapper接口绑定原理(代理设计模式) ,
1.获取本地InputStream对象(mybatis配置文件)
2.调用SqlSessionFactoryBuilder
再使用XMLConfigBuilder解析mybatis配置文件,装配到Configuration中。
将配置文件中的Mapper添加到Configuration mapperRegistry实现注册。
备注:mapperRegistry存放当前所有的mapper文件。
3.使用configuration获取默认的DefaultSqlSessionFactory
评论