mybatis中的土鸡杂鱼
mybatis中的土鸡杂鱼
1、mapper接口为什么要和mapper.xml在同一个路径下?
为什么在单独学习使用mybatis的时候,强烈推荐要放在同一个路径下?
首先看下我的配置文件写法如下:
<mappers>
<package name="com.guang.mybatis.mapper"/>
</mappers>
也就是说,mapper接口都是com.guang.mybatis.mapper路径下,那么我们的配置文件也要在这个路径下。
那么按照这个思路来,解析过程在XMLMapperBuilder中org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement中
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
}
// ....省略代码
然后来到
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
// 加载xml
loadXmlResource();
//...省略代码
那么仔细看下下面这段代码:
private void loadXmlResource() {
// Spring may not know the real resource name so we check a flag
// to prevent loading again a resource twice
// this flag is set at XMLMapperBuilder#bindMapperForNamespace
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
// 根据类的全限定路径找对应的xml文件
String xmlResource = type.getName().replace('.', '/') + ".xml";
// #1347
InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
if (inputStream == null) {
// Search XML mapper that is not in the module but in the classpath.
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e2) {
// ignore, resource is not required
}
}
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}
看到这里,就知道了为什么说要mapper接口和mapper.xml的路径保持一致的原因了。
2、主键生成为什么配置一个字段就可以?
若数据库支持自动生成主键(比如 MySQL 和 SQL Server),则可以设置 useGeneratedKeys=“true”,表明使用自增主键获取主键值策略,然后再利用 keyProperty 属性指定对应的主键属性,也就是 Mybatis 获取到主键值后,将这个值封装给 JavaBean 的哪个属性。
<insert id="addEmp" databaseId="mysql" parameterType="employee" useGeneratedKeys="true" keyProperty="id">
insert into tbl_employee (id, last_name, email, gender)
values (#{id}, #{lastName}, #{email}, #{gender});
</insert>
获取自增主键值:
@Test
public void test04() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
try{
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Employee employee = new Employee();
employee.setLastName("lig");
employee.setEmail("lidaye@123.com");
employee.setGender("1");
boolean addEmp = mapper.addEmp(employee);
System.out.println(addEmp);
// 直接通过 getId() 方法来获取自增主键值
System.out.println(employee.getId());
sqlSession.commit();
} finally {
sqlSession.close();
}
}
[main][com.example.mapper.EmployeeMapper.addEmp]-[DEBUG] ==> Preparing: insert into tbl_employee (id, last_name, email, gender) values (?, ?, ?, ?);
[main][com.example.mapper.EmployeeMapper.addEmp]-[DEBUG] ==> Parameters: null, wangwu(String), wangwu@123.com(String), 1(String)
[main][com.example.mapper.EmployeeMapper.addEmp]-[DEBUG] <== Updates: 1
true
9
其中“9”为获取到的自增主键值。
原理
下面看在解析过程中org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode
// ...省略代码
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
// ...省略代码
如果在对应的增伤改查语句中写了useGeneratedKeys=true,那么这里就会开始使用Jdbc3KeyGenerator.INSTANCE
那么既然会使用到Jdbc3KeyGenerator,那么看一下在哪里执行的。
首先追踪到org.apache.ibatis.executor.statement.BaseStatementHandler#BaseStatementHandler
if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
protected void generateKeys(Object parameter) {
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
ErrorContext.instance().store();
keyGenerator.processBefore(executor, mappedStatement, null, parameter);
ErrorContext.instance().recall();
}
但是发现org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator#processBefore中什么都没有做
然后在org.apache.ibatis.executor.statement.PreparedStatementHandler#instantiateStatement
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
return connection.prepareStatement(sql);
} else {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
关于这里的KeyColumns参考文章:https://www.cnblogs.com/woyujiezhen/p/11643944.html
但是在MySQL中,我们并没有配置这个东西。所以这里仅仅只是创建了一个预编译的PreparedStatement,并且设置是有返回生成的主键的。
然后找到了org.apache.ibatis.executor.statement.PreparedStatementHandler#update
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
int rows = ps.getUpdateCount();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
具体的就可以看下org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator#processBatch
public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
// 首先获取得到配置的keyProperty的值,最终会在assignKeys中将方法进行赋值进去
final String[] keyProperties = ms.getKeyProperties();
if (keyProperties == null || keyProperties.length == 0) {
return;
}
try (ResultSet rs = stmt.getGeneratedKeys()) {
final ResultSetMetaData rsmd = rs.getMetaData();
final Configuration configuration = ms.getConfiguration();
if (rsmd.getColumnCount() < keyProperties.length) {
// Error?
} else {
assignKeys(configuration, rs, rsmd, keyProperties, parameter);
}
} catch (Exception e) {
throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
}
}
3、为什么默认使用的是预编译PreparedStatement?
这里就和上面一样的位置:
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
4、分页对象RowBounds和ResultContext
在org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleRowValuesForSimpleResultMap方法中有一个细节的地方
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
// 默认结果集处理
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
ResultSet resultSet = rsw.getResultSet();
// 跳过指定行
skipRows(resultSet, rowBounds);
// 利用ResultContext来进行选择是否停止
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
其实本质上的原因,我们不会用到这里的东西。因为都是默认的,但是如果使用了Sqlsession提供的API中包含了有RowBounds和ResultHandler,那么我们可以来实现自定义处理。
根据官方描述是为了防止数据量太大的情况,但是我们一般来说,只要SQL写的好,就能够避免掉这种情况。
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
// 因为做了自定义处理,所以结果集应该另外选择了一个集合保存。故返回值为void
void select(String statement, Object parameter, ResultHandler handler);