当前位置: 首页 > news >正文

Mybatis框架源码笔记(七)之Mybatis中类型转换模块(TypeHandler)解析

1、JDBC的基本操作回顾

这里使用伪代码概括一下流程:

    1. 对应数据库版本的驱动包自行下载加载驱动类
(Class.forName("com.mysql.cj.jdbc.Driver"))
    1. 创建Connection连接:
conn = DriverManager.getConnection("jdbc:mysql://数据库IP:port/数据库名称?useUnicode=true&characterEncoding=utf8", "用户名", "用户密码");
    1. 准备SQL语句:
String sql = "select * from lib_book where book_id = ?";
    1. 创建预处理语句对象
PreparedStatement ps = conn.prepareStatement(sql)
    1. 输入参数处理
// 需要根据参数索引位置和参数类型替换对应占位符索引位置上的?为实际的参数值 例如 ps.setInt(1, 12)
ps.setInt(1, 10);
ps.setString(2, "老人与海");
    1. 执行SQL语句

查询操作:

ResultSet resultSet = ps.executeQuery();

修改操作:

ps.execute();
    1. 处理返回结果集
while(resultSet.next()){ // 取出一行数据来进行处理,映射成java实体类 	LibBook book = new LibBook(); 	 book.setBookId(resultSet.getLong(1));book.setBookIndexNo(resultSet.getString(2));book.setBookName(resultSet.getString(3));book.setBookAuthor(resultSet.getString(4));book.setBookPublisher(resultSet.getString(6));book.setBookCateId(resultSet.getInt(7));book.setBookStock(resultSet.getInt(8)); }
    1. 依次关闭打开的所有句柄对象(ResultSet、PreparedStatement、Connection)
 if(statement != null) {try {statement.close();} catch (SQLException e) {e.printStackTrace();}}if(conn != null) {try {conn.close();} catch (SQLException e) {e.printStackTrace();}}

2、Myabtis框架对于JDBC操作的简化

总结一下Mybatis框架的使用流程:

1) pom.xml文件中引入Mybatis、日志框架、单元测试框架及数据库驱动依赖2) 编写mybatis-config.xml全局配置文件、日志配置文件3) 编写Mapper层接口4) 编写Mapper层接口对应的xml映射文件, OK搞定。

单元测试的使用过程:

1) 创建一个SqlSessionFactoryBuilder对象:SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();	2) 创建一个SqlSessionFactory对象:SqlSessionFactory factory = factoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml")); 3) 创建一个SqlSession对象:SqlSession sqlSession = factory.openSession();4) 获取Mapper层指定接口的动态代理对象:LibBookMapper mapper = sqlSession.getMapper(LibBookMapper.class);5) 调用接口方法获取返回结果即可: List<LibBook> list = mapper.selectAllBook();

对比操作, 我们发现使用Mybatis框架之后,我们不需要在关注JDBC的具体操作操作过程了,输入参数和返回结果的映射转换
不用我们再手动处理了, 资源的释放也不需要我们手动处理了,只需要调用简单的应用层接口就可以完成所有的操作,太疯狂了。

但是对于一个合格的开发人员, 我们对于框架的API熟练使用, 还要对其中的原理清楚了解, 这样才能再出现问题时,我们可以修改和扩展原有框架完成我们想要的功能, 扯远了, 今天我们主要来聊一聊Mybatis框架究竟是如何封装JDBC中处理输入参数和返回结果集的类型转换的功能的?

3、Myabtis框架的类型转换模块

类型转换模块属于Mybatis框架的基础支撑层模块,主要实现数据库中数据Java对象中的属性双向映射
主要就是在以下两种场景钟会使用到:

 1)在PreparedStatement为SQL语句绑定参数时,需要从Java类型转换为JDBC类型;2)从ResultSet结果集中获取数据时,则需要从JDBC类型转换为Java类型;

3.1 如何完成javaType和JDBCType类型互转

在这里插入图片描述

3.2 Mybatis的设计实现

在这里插入图片描述

3.2.1 TypeHandler接口

MyBatis框架中提供的所有的类型转换器实现类都继承了TypeHandler接口,在TypeHandler接口中定义了类型转换器的最基本的功能(处理输入参数和输出参数)。

/**    Copyright 2009-2023 the original author or authors.**    Licensed under the Apache License, Version 2.0 (the "License");*    you may not use this file except in compliance with the License.*    You may obtain a copy of the License at**       http://www.apache.org/licenses/LICENSE-2.0**    Unless required by applicable law or agreed to in writing, software*    distributed under the License is distributed on an "AS IS" BASIS,*    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.*    See the License for the specific language governing permissions and*    limitations under the License.*/
package org.apache.ibatis.type;import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;/*** @author Clinton Begin* 类型处理器*/
public interface TypeHandler<T> {/*** 完成SQL语句中实际参数替换占位符的方法*/void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;/*** Gets the result.* 通过数据库列名称从ResultSet中获取结果数据** @param rs*          the rs* @param columnName*          Column name, when configuration <code>useColumnLabel</code> is <code>false</code>* @return the result* @throws SQLException*           the SQL exception*/T getResult(ResultSet rs, String columnName) throws SQLException;/*** 通过数据库列索引从ResultSet中获取结果数据*/T getResult(ResultSet rs, int columnIndex) throws SQLException;/*** 通过数据库列索引从存储过程的执行书写出结果中获取结果数据*/T getResult(CallableStatement cs, int columnIndex) throws SQLException;}

在这里插入图片描述

3.2.2 BaseTypeHandler实现类

这个基础实现类中只提供了通用了类型转换的基本实现,并在原有功能上进行了扩展。
在这里插入图片描述
但是对于不同的数据类型的互转还是要根据类型进行针对性处理,比如数字类型、字符串类型、日期类型等等,处理方法肯定不同,mybatis框架提供了这些常用类型的转换器实现

类型处理器Java 类型JDBC 类型
BooleanTypeHandlerjava.lang.Boolean, boolean数据库兼容的 BOOLEAN
ByteTypeHandlerjava.lang.Byte, byte数据库兼容的 NUMERIC 或 BYTE
ShortTypeHandlerjava.lang.Short, short数据库兼容的 NUMERIC 或 SMALLINT
IntegerTypeHandlerjava.lang.Integer, int数据库兼容的 NUMERIC 或 INTEGER
LongTypeHandlerjava.lang.Long, long数据库兼容的 NUMERIC 或 BIGINT
FloatTypeHandlerjava.lang.Float, float数据库兼容的 NUMERIC 或 FLOAT
DoubleTypeHandlerjava.lang.Double, double数据库兼容的 NUMERIC 或 DOUBLE
BigDecimalTypeHandlerjava.math.BigDecimal数据库兼容的 NUMERIC 或 DECIMAL
StringTypeHandlerjava.lang.StringCHAR, VARCHAR
ClobReaderTypeHandlerjava.io.Reader-
ClobTypeHandlerjava.lang.StringCLOB, LONGVARCHAR
NStringTypeHandlerjava.lang.StringNVARCHAR, NCHAR
NClobTypeHandlerjava.lang.StringNCLOB
BlobInputStreamTypeHandlerjava.io.InputStream-
ByteArrayTypeHandlerbyte[]数据库兼容的字节流类型
BlobTypeHandlerbyte[]BLOB, LONGVARBINARY
DateTypeHandlerjava.util.DateTIMESTAMP
DateOnlyTypeHandlerjava.util.DateDATE
TimeOnlyTypeHandlerjava.util.DateTIME
SqlTimestampTypeHandlerjava.sql.TimestampTIMESTAMP
SqlDateTypeHandlerjava.sql.DateDATE
SqlTimeTypeHandlerjava.sql.TimeTIME
ObjectTypeHandlerAny OTHER或未指定类型
EnumTypeHandlerEnumeration TypeVARCHAR 或任何兼容的字符串类型,用来存储枚举的名称(而不是索引序数值)
EnumOrdinalTypeHandlerEnumeration Type任何兼容的 NUMERIC 或 DOUBLE 类型,用来存储枚举的序数值(而不是名称)。
SqlxmlTypeHandlerjava.lang.StringSQLXML
InstantTypeHandlerjava.time.InstantTIMESTAMP
LocalDateTimeTypeHandlerjava.time.LocalDateTimeTIMESTAMP
LocalDateTypeHandlerjava.time.LocalDateDATE
LocalTimeTypeHandlerjava.time.LocalTimeTIME
OffsetDateTimeTypeHandlerjava.time.OffsetDateTimeTIMESTAMP
OffsetTimeTypeHandlerjava.time.OffsetTimeTIME
ZonedDateTimeTypeHandlerjava.time.ZonedDateTimeTIMESTAMP
YearTypeHandlerjava.time.YearINTEGER
MonthTypeHandlerjava.time.MonthINTEGER
YearMonthTypeHandlerjava.time.YearMonthVARCHAR 或 LONGVARCHAR
JapaneseDateTypeHandlerjava.time.chrono.JapaneseDateDATE

这里通过一个具体的基础数据类型BooleanTypeHandler类来进行说明

/**    Copyright 2009-2022 the original author or authors.**    Licensed under the Apache License, Version 2.0 (the "License");*    you may not use this file except in compliance with the License.*    You may obtain a copy of the License at**       http://www.apache.org/licenses/LICENSE-2.0**    Unless required by applicable law or agreed to in writing, software*    distributed under the License is distributed on an "AS IS" BASIS,*    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.*    See the License for the specific language governing permissions and*    limitations under the License.*/
package org.apache.ibatis.type;import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;/*** @author Clinton Begin*/
public class BooleanTypeHandler extends BaseTypeHandler<Boolean> {/*** 预处理语句中的非null参数转换(null参数在哪里处理的呢, 在父类BaseTypeHandler里面处理了)*/@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, Boolean parameter, JdbcType jdbcType)throws SQLException {ps.setBoolean(i, parameter);}/*** ResultSet结果集里面jdbcType返回为null时通过调用该方法"传递列名方式"转换成Java类中对应属性的javaType*/@Overridepublic Boolean getNullableResult(ResultSet rs, String columnName)throws SQLException {boolean result = rs.getBoolean(columnName);return !result && rs.wasNull() ? null : result;}/*** ResultSet结果集里面jdbcType返回为null时通过调用该方法"传递列索引方式"转换成Java类中对应属性的javaType*/@Overridepublic Boolean getNullableResult(ResultSet rs, int columnIndex)throws SQLException {boolean result = rs.getBoolean(columnIndex);return !result && rs.wasNull() ? null : result;}/*** 存储过程执行结果转换成javaType类型*/@Overridepublic Boolean getNullableResult(CallableStatement cs, int columnIndex)throws SQLException {boolean result = cs.getBoolean(columnIndex);return !result && cs.wasNull() ? null : result;}
}

3.2.3 TypeHandlerRegistry注册器

Mybatis框架提供了很多的类型转换器实现给我们使用, 这些类型转换器对象肯定不是需要我们自己进行创建的, 肯定是在Mybatis集成到我们的项目中时就已经完成所有
默认的类型转换器的实例化的,那么Myabtis框架是怎么保存这些对象的么? 这些对象又是什么时候完成的实例化操作的么?
就是通过 TypeHandlerRegistry类型转换器完成的。
在这里插入图片描述在TypeHandlerRegistry注册器的构造器中完成了java中常用数据类型与jdbc对应数据类型转换时需要用到的TypeHandler类对象的创建和注册
在这里插入图片描述

3.2.4 TypeAliasRegistry别名注册器

MyBatis框架的应用的时候会经常用到别名,这能大大简化我们的代码,其实在MyBatis中是通过TypeAliasRegistry类管理的。在TypeAliasRegistry类的构造方法中会注入系统常见类型的别名。
在这里插入图片描述
别名注册器中的核心方法如下:
在这里插入图片描述

  public void registerAlias(String alias, Class<?> value) {if (alias == null) {throw new TypeException("The parameter alias cannot be null");}// issue #748  将别名先转成小写字母String key = alias.toLowerCase(Locale.ENGLISH);// 判断别名是否存在if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");}// 将别名添加到aliasMap集合中去typeAliases.put(key, value);}

Mybatis自定义别名注册的两种方式:

  • 第一 配置文件方式 (mybatis-config.xml)
<typeAliases><package name="com.baidu"/>
</typeAliases> 
  • 第二 注解方式 @Alias
@Alias("People")
public class SysPeople {private String name;
}

自定义别名注册也是就是通过这个registerAliases(String packageName, Class<?> superType) 方法来进行解析注册的。

  /*** 通过package指定别名路径和通过@Alisa注解来注册别名的方法* 例如我们在全局配置文件中配置的 <typeAliases> <package name="com.baidu"/></typeAliases> 就是通过这个方法来解析处理的*  还有我们使用@Alias()注解为类注册的别名都是通过这个方法来完成注册的*/public void registerAliases(String packageName, Class<?> superType) {ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();resolverUtil.find(new ResolverUtil.IsA(superType), packageName);Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();for (Class<?> type : typeSet) {// Ignore inner classes and interfaces (including package-info.java)// Skip also inner classes. See issue #6if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {registerAlias(type);}}}public void registerAlias(Class<?> type) {String alias = type.getSimpleName();Alias aliasAnnotation = type.getAnnotation(Alias.class);if (aliasAnnotation != null) {alias = aliasAnnotation.value();}registerAlias(alias, type);}

3.2.5 如何自定义类型装换器

你可以重写已有的类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。 具体做法为:
实现 org.apache.ibatis.type.TypeHandler 接口,
或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler, 并且可以(可选地)将它映射到一个 JDBC 类型。
具体的示例官方已经提供了示例代码, 有兴趣可以自行观看文档, 这里贴上传送门:自定义类型转换器文档及示例代码地址:https://mybatis.org/mybatis-3/zh/configuration.html#typeHandlers

3.3 Mybatis中Typehandler的应用

不管Mybatis框架还是SQL语句的执行流程经过这么长时间的接触, 大家肯定越来越熟悉的, 关于SQL语句输入参数的转换及SQL语句执行结果的处理流程节点大家应该能条件反射的说出来, 肯定是在SQL语句执行的时候进行处理的, Myabtis框架中执行SQL语句都是依赖Executor来完成的.

关于SQL的参数处理及ResultSet的处理肯定也在这个接口的某个实现类中完成的, 这里就不卖关子了, 直接亮剑

3.3.1 输入参数处理

主要涉及的核心类就是SimpleExecutorPreparedStatementHandlerDefaultParameterHandler,来看看每个类中关于输入参数处理的核心方法。

  • SimpleExecutor

SQL语句预处理的核心方法如下

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;// 创建数据库连接会话,从这里可以看出数据库连接的创建时延迟创建的, 在真正使用的时候才会创建,这里我们可以做扩展,// 数据库的读写分离可以通过这个特性通过扩展实现Connection connection = getConnection(statementLog);// 创建预处理SQL语句对象stmt = handler.prepare(connection, transaction.getTimeout());// 预处理SQL语句对象中的参数占位符替换处理handler.parameterize(stmt);return stmt;}
三件事
1、创建数据库连接会话
2、创建预处理SQL语句对象,
3、如果SQL语句中有查询条件,使用了参数占位符, 就将实际的参数替换成真实的输入参数

看看上面这段代码不就是对应JDBC里面这段代码么,对不对
在这里插入图片描述
只是Mybatis框架做了面向对象层次的封装, 其实核心就是上面那三件事
SimpleExecutor

  • PreparedStatementHandler

这里省略了关于handler.parameterize()方法的调用过程的解析, 直接说明一下吧, 就是运用了模板方法模式完成了StatementHandler的参数替换的功能,因为MappedStatement的StatementType默认的类型是PREPARED, 所以这里调用的是
PreparedStatementHandlerparameterize()方法,接下来看看真正处理输入参数的方法

  @Overridepublic void parameterize(Statement statement) throws SQLException {parameterHandler.setParameters((PreparedStatement) statement);}

上面的方法调用了ParameterHandler接口的setParameters()方法
在这里插入图片描述

3.3.2 ParameterHandler对象是什么时候创建的?

在SimpleExecutor对象的doQuery()或者doUpdate()方法中调用了创建StatementHanlderParameterHandlerResultSetHandler对象的入口,方法如下

  @Overridepublic <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();// StatementHanlder、ParameterHandler、ResultSetHandler创建的入口StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);// 预处理SQL语句对象创建stmt = prepareStatement(handler, ms.getStatementLog());// 执行SQL语句并处理结果后返回return handler.query(stmt, resultHandler);} finally {closeStatement(stmt);}}

来看看configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);这个方法

 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {// 创建的statementhandler对象是一个RoutingStatementHandler()对象StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler;}

看看RoutingStatementHandler的构造方法, 如下:
在这里插入图片描述
再看看RoutingStatementHandler的构造方法, 如下:
在这里插入图片描述
再看看BaseStatementHandler的构造方法,如下:
在这里插入图片描述

结论

在创建实际Statement对象对应的StatementHandler时, 完成了ParameterHandler和ResultHandler对象的创建。

3.3.3 ParameterHandler/ResultSethandler对象是谁?

接着上面往下聊, 看看3.3.2 BaseStatementHandler的构造方法中下面两行代码的执行过程

    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);

Configuration类中对应的方法如下:

  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {// 这里我们使用的是**mapper.xml文件声明的SQL语句,所以这里的的LanguageDriver肯定是XMLLanguageDriver// XMLLanguageDriver.createParameterHandler()方法会创建一个DefaultParameterHandler对象ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);// 这里我们可以集成参数处理的插件来扩展入参处理的功能parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);return parameterHandler;}public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,ResultHandler resultHandler, BoundSql boundSql) {// Mybatis框架默认创建的就是DefaultResultSetHandler对象  ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);// 这里我们可以集成结果集处理的插件来扩展结果集处理的功能resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);return resultSetHandler;}

在这里插入图片描述
结论

如果没有自定义插件, 那么Mybatis框架会为我们创建框架提供的默认的DefaultParameterHandler和DefaultResultHandler对象来实现
SQL语句入参和查询结果集ResultSet转换的功能

3.3.4 DefaultParameterHandler中入参替换的方法实现

  • DefaultParameterHandler
  /*** 替换SQL语句中的占位符为实际传入的参数值的方法*/@Overridepublic void setParameters(PreparedStatement ps) {ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());// 从BoundSql对象中获取到参数映射对象集合List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();if (parameterMappings != null) {for (int i = 0; i < parameterMappings.size(); i++) {// 依次取出parameterMapping对象ParameterMapping parameterMapping = parameterMappings.get(i);// 保证当前处理的parameterMapping对象都是输入参数if (parameterMapping.getMode() != ParameterMode.OUT) {Object value;// 获取当前处理的parameterMapping对象的属性名称String propertyName = parameterMapping.getProperty();// 如果BoundSql对象的附加参数对象中包含该属性名称, 直接从BoundSql对象的附加参数对象中获取到该属性KEY对应的值if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional paramsvalue = boundSql.getAdditionalParameter(propertyName);// 如果ParameterObject为空,说明没有传值,值直接就为null} else if (parameterObject == null) {value = null;// 如果类型注册器中有该参数对象对应的类型处理器,则该参数取值就是parameterObject} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {value = parameterObject;// 以上都不满足,就创建一个元数据对象,然后从元数据对象汇总通过属性获取到对应的取值} else {MetaObject metaObject = configuration.newMetaObject(parameterObject);value = metaObject.getValue(propertyName);}// 获取当前parameterMapping对象的类型处理器TypeHandler typeHandler = parameterMapping.getTypeHandler();// 获取当前parameterMapping对象的JDBC数据类型JdbcType jdbcType = parameterMapping.getJdbcType();// 如果参数输入值为null并且数据库数据类型为null,就将jdbcType类型设置为OTHER类型if (value == null && jdbcType == null) {jdbcType = configuration.getJdbcTypeForNull();}try {// 为什么这里是使用i + 1?// insert into t_user(name, age, gender, email) value(?, ?, ?, ?)// 因为解析出来的带占位的sql语法中的?参数的计数是从1开始的, 不是从0开始的// 调用typeHandler的替换参数的方法替换到SQL语句中目标位置上占位符上为输入的参数值typeHandler.setParameter(ps, i + 1, value, jdbcType);} catch (TypeException | SQLException e) {throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);}}}}}

3.3.5 输出结果处理

主要涉及的核心类就是SimpleExecutorPreparedStatementHandlerDefaultResultSetHandler,来看看每个类中关于查询结果集ResultSet处理的核心方法。

  • SimpleExecutor
  @Overridepublic <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);stmt = prepareStatement(handler, ms.getStatementLog());// 调用Statementhandler的query()方法执行查询,获取查询结果return handler.query(stmt, resultHandler);} finally {closeStatement(stmt);}}
  • PreparedStatementHandler

调用resultSetHandler.handleResultSets(ps)方法处理查询结果集ResultSet

  @Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.execute();// 调用DefaultResultHandler的handleResultSets方法处理SQL语句的执行结果return resultSetHandler.handleResultSets(ps);}

3.3.6 DefaultResultSetHandler中ResultSet结果集转换的方法实现

handleResultSets()方法

  //// HANDLE RESULT SETS//@Overridepublic List<Object> handleResultSets(Statement stmt) throws SQLException {ErrorContext.instance().activity("handling results").object(mappedStatement.getId());final List<Object> multipleResults = new ArrayList<>();int resultSetCount = 0;ResultSetWrapper rsw = getFirstResultSet(stmt);List<ResultMap> resultMaps = mappedStatement.getResultMaps();int resultMapCount = resultMaps.size();validateResultMapsCount(rsw, resultMapCount);while (rsw != null && resultMapCount > resultSetCount) {ResultMap resultMap = resultMaps.get(resultSetCount);// 处理每一行数据从ResultSet转换成java实体类的方法handleResultSet(rsw, resultMap, multipleResults, null);rsw = getNextResultSet(stmt);cleanUpAfterHandlingResultSet();resultSetCount++;}String[] resultSets = mappedStatement.getResultSets();if (resultSets != null) {while (rsw != null && resultSetCount < resultSets.length) {ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);if (parentMapping != null) {String nestedResultMapId = parentMapping.getNestedResultMapId();ResultMap resultMap = configuration.getResultMap(nestedResultMapId);handleResultSet(rsw, resultMap, null, parentMapping);}rsw = getNextResultSet(stmt);cleanUpAfterHandlingResultSet();resultSetCount++;}}return collapseSingleResultList(multipleResults);}

处理每一行数据从ResultSet转换成java实体类的方法

 //// GET VALUE FROM ROW FOR NESTED RESULT MAP//private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix, Object partialObject) throws SQLException {final String resultMapId = resultMap.getId();Object rowValue = partialObject;if (rowValue != null) {final MetaObject metaObject = configuration.newMetaObject(rowValue);putAncestor(rowValue, resultMapId);applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);ancestorObjects.remove(resultMapId);} else {final ResultLoaderMap lazyLoader = new ResultLoaderMap();rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {final MetaObject metaObject = configuration.newMetaObject(rowValue);boolean foundValues = this.useConstructorMappings;if (shouldApplyAutomaticMappings(resultMap, true)) {// 自动完成jdbcType到javaType的映射foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;}// 如果数据库字段和实体类属性无法自动映射, 需要通过该方法完成转换foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;putAncestor(rowValue, resultMapId);// 如果存在关联查询时, 需要完成来自其他数据表的ResultSet的转换处理foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;ancestorObjects.remove(resultMapId);foundValues = lazyLoader.size() > 0 || foundValues;rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;}if (combinedKey != CacheKey.NULL_CACHE_KEY) {nestedResultObjects.put(combinedKey, rowValue);}}return rowValue;}

可以自动映射时根据对应的TypeHandler返回对应类型的值。

  private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);boolean foundValues = false;if (!autoMapping.isEmpty()) {for (UnMappedColumnAutoMapping mapping : autoMapping) {final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);if (value != null) {foundValues = true;}if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {// gcode issue #377, call setter on nulls (value is not 'found')metaObject.setValue(mapping.property, value);}}}return foundValues;}

根据Property调用对应的TypeHandler返回对应类型的值。

  private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)throws SQLException {if (propertyMapping.getNestedQueryId() != null) {return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);} else if (propertyMapping.getResultSet() != null) {addPendingChildRelation(rs, metaResultObject, propertyMapping);   // TODO is that OK?return DEFERRED;} else {final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);return typeHandler.getResult(rs, column);}}

相关文章:

Mybatis框架源码笔记(七)之Mybatis中类型转换模块(TypeHandler)解析

1、JDBC的基本操作回顾 这里使用伪代码概括一下流程: 对应数据库版本的驱动包自行下载加载驱动类 (Class.forName("com.mysql.cj.jdbc.Driver"))创建Connection连接: conn DriverManager.getConnection("jdbc:mysql://数据库IP:port/数据库名称?useUnico…...

论文阅读《Block-NeRF: Scalable Large Scene Neural View Synthesis》

论文地址&#xff1a;https://arxiv.org/pdf/2202.05263.pdf 复现源码&#xff1a;https://github.com/dvlab-research/BlockNeRFPytorch 概述 Block-NeRF是一种能够表示大规模环境的神经辐射场&#xff08;Neural Radiance Fields&#xff09;的变体&#xff0c;将 NeRF 扩展到…...

【Matlab】如何设置多个y轴

MTALAB提供了创建具有两个y轴的图&#xff0c;通过help yyaxis就能看到详细的使用方式。 但是如果要实现3个及以上y轴的图&#xff0c;就没有现成的公式使用了&#xff0c;如下图所示。 具体代码 % 数据准备 x10:0.01:10; y1sin(x1); x20:0.01:10; y2cos(x2); x30:0.01:10;…...

圆桌(满足客人空座需求,合理安排客人入座圆桌,准备最少的椅子)

CSDN周赛第30期第四题算法解析。 (本文获得CSDN质量评分【91】)【学习的细节是欢悦的历程】Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”教程《 python 完全自学教程》&#xff0c;不仅仅是基础那么简单…… 地址&#xff1a;https://lq…...

如何入门大数据?

我们首先了解一下大数据到底是什么~ 大数据开发做什么&#xff1f; 大数据开发分两类&#xff0c;编写Hadoop、Spark的应用程序和对大数据处理系统本身进行开发。 大数据开发工程师主要负责公司大数据平台的开发和维护、相关工具平台的架构设计与产品开发、网络日志大数据分…...

如何在Vite项目中使用Lint保证代码质量

通常,大型前端项目都是多人参与的,由于开发者的编码习惯和喜好都不尽相同,为了降低维护成本,提高代码质量,所以需要专门的工具来进行约束,并且可以配合一些自动化工具进行检查,这种专门的工具称为Lint,可能大家接触得最多就是ESLint。 对于实现自动化代码规范检查及修…...

Spark高手之路1—Spark简介

文章目录Spark 概述1. Spark 是什么2. Spark与Hadoop比较2.1 从时间节点上来看2.2 从功能上来看3. Spark Or Hadoop4. Spark4.1 速度快4.2 易用4.3 通用4.4 兼容5. Spark 核心模块5.1 Spark-Core 和 弹性分布式数据集(RDDs)5.2 Spark SQL5.3 Spark Streaming5.4 Spark MLlib5.5…...

社科院与杜兰大学金融管理硕士项目——人生没有太晚的开始,不要过早的放弃

经常听到有人问&#xff0c;“我都快40了&#xff0c;现在学车晚不晚呢”“现在考研晚不晚&#xff1f;”“学画画晚不晚&#xff1f;”提出这些疑问的人&#xff0c;往往存在拖延&#xff0c;想法只停留在想的阶段&#xff0c;从来不去行动。当看到周边行动起来的人开始享受成…...

Spatial-Temporal Graph ODE Networks for Traffic Flow Forecasting

Spatial-Temporal Graph ODE Networks for Traffic Flow Forecasting 摘要 交通流量的复杂性和长范围时空相关性是难点 经典现存的工作&#xff1a; 1.利用浅图神经网络&#xff08;shallow graph convolution networks&#xff09;和 时间提取模块去分别建模空间和时间依赖…...

IP协议+以太网协议

在计算机网络体系结构的五层协议中&#xff0c;第三层就是负责建立网络连接&#xff0c;同时为上层提供服务的一层&#xff0c;网络层协议主要负责两件事&#xff1a;即地址管理和路由选择&#xff0c;下面就网络层的重点协议做简单介绍~~ IP协议 网际协议IP是TCP/IP体系中两…...

可视化组件届的仙女‖蝴蝶结图、玫瑰环图、小提琴图

在上一篇内容中为大家介绍了几个堪称可视化组件届吴彦祖的高级可视化图表。既然帅哥有了&#xff0c;怎么能少得了美女呢&#xff1f;今天就为大家介绍几个可视化组件届的“美女姐姐”&#xff0c;说一句是组件届的刘亦菲不为过。蝴蝶结图蝴蝶结图因其形似蝴蝶结而得名&#xf…...

人的高级认知:位置感

你知道吗&#xff1f;人有个高级认知&#xff1a;位置感 位置感是啥&#xff1f;咋提高位置感&#xff1f; 趣讲大白话&#xff1a;知道自己几斤几两 【趣讲信息科技99期】 ******************************* 位置感 就是对自己所处环境和自身存在的领悟 属于人生智慧 来源于阅历…...

MATLAB——信号的采样与恢复

**题目&#xff1a;**已知一个连续时间信号 其中&#xff1a;f01HZ&#xff0c;取最高有限带宽频率fm5f0。分别显示原连续时间信号波形和 3种情况下抽样信号的波形。并画出它们的幅频特性曲线&#xff0c;并对采样后的信号进行恢复。 step1.绘制出采样信号 这部分相对简单…...

Docker Nginx 反向代理

最近在系统性梳理网关的知识&#xff0c;其中网关的的功能有一个是代理&#xff0c;正好咱们常用的Nginx也具备次功能&#xff0c;今天正好使用Nginx实现一下反向代理&#xff0c;与后面网关的代理做一个对比&#xff0c;因为我使用的docker安装的Nginx&#xff0c;与直接部署N…...

手把手教你实现书上的队列,进来试试?

一.队列的基本概念队列的定义队列&#xff08;queue&#xff09;是只允许在一端进行插入操作&#xff0c;而在另一端进行删除操作的线性表。队列是一种先进先出&#xff08;First In First Out&#xff09;的线性表&#xff0c;简称FIFO。允许插入的一端称为队尾&#xff0c;允…...

【springboot】springboot介绍

学习资料 SpringBoot 语雀 (yuque.com)【尚硅谷】SpringBoot2零基础入门教程&#xff08;spring boot2干货满满&#xff09;_哔哩哔哩_bilibiliSpringBoot2核心技术与响应式编程: SpringBoot2核心技术与响应式编程 (gitee.com) Spring 和Springboot 1、Spring能做什么 1.1…...

PMP项目管理项目整合管理

目录1 项目整合管理概述2 制定项目章程3 制定项目管理计划4 指导与管理项目工作5 管理项目知识6 监控项目工作7 实施整体变更控制8 结束项目或阶段1 项目整合管理概述 项目整合管理包括对隶属于项目管理过程组的各种过程和项目管理活动进行识别、定义、组合、统一和协调的各个…...

ADS中导入SPICE模型

这里写目录标题在官网中下载SPICE模型ADS中导入SPICE模型在官网中下载SPICE模型 英飞凌官网 ADS中导入SPICE模型 点击option&#xff0c;设置导入选项 然后点击ok 如果destination选择当前的workspace&#xff0c;那么导入完成之后如下&#xff1a; &#xff08;推荐使用…...

C++:异常

在学习异常之前&#xff0c;来简单总结一下传统的处理错误的方式&#xff1a; 1. 终止程序&#xff0c;如assert&#xff0c;缺陷&#xff1a;用户难以接受。如发生内存错误&#xff0c;除0错误时就会终止程序。 2. 返回错误码&#xff0c;缺陷&#xff1a;需要程序员自己去查找…...

3.初识Vue

目录 1 vue 浏览器调试工具 1.1 安装 1.2 配置 2 数据驱动视图与双向数据绑定 3 简单使用 3.1 下载 3.2 将信息渲染到DOM上 4 使用vue浏览器调试工具 5 vue指令 1 vue 浏览器调试工具 chrome可能是我浏览器的原因&#xff0c;装上用不了&#xff0c;我们使…...

【C语言复习】程序的编译与链接

程序的编译与链接写在前面程序的编译与链接编译的过程程序编译环境程序执行过程编译链接的过程预处理预处理符号#define条件编译写在前面 程序的编译与链接是C语言中非常重要的一节。关键点在于详解C语言的程序编译和链接、宏的定义和与函数的区别、条件编译等知识。 程序的编…...

Golang sql 事务如何进行分层

在写代码过程中遇到了需要使用gorm执行sql事务的情况&#xff0c;研究了一下各位大佬的实现方案&#xff0c;结合了自身遇到的问题&#xff0c;特此记录。 代码架构介绍 . ├── apis ├── config ├── internal │ ├── constant │ ├── controller │ ├──…...

linux系统openssh升级

linux系统openssh升级 说明 整个过程不需要卸载原先的openssl包和openssh的rpm包。本文的环境都是系统自带的openssh&#xff0c;没有经历过手动编译安装方式。如果之前有手动编译安装过openssh&#xff0c;请参照本文自行测试是否能成功。 如果严格参照本文操作&#xff0c;保…...

力扣-求关注者的数量

大家好&#xff0c;我是空空star&#xff0c;本篇带大家了解一道简单的力扣sql练习题。 文章目录前言一、题目&#xff1a;1729. 求关注者的数量二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.正确示范④提交SQL运行结果5.正确…...

近红外荧光染料修饰氨基IR 825 NH2,IR 825-Amine,IR-825 NH2

IR 825 NH2&#xff0c;IR 825-NH2&#xff0c;IR825 Amine&#xff0c;IR825-Amine&#xff0c;新吲哚菁绿-氨基&#xff0c;荧光染料修饰氨基产品规格&#xff1a;1.CAS号&#xff1a;N/A2.包装规格&#xff1a;10mg&#xff0c;25mg&#xff0c;50mg&#xff0c;包装灵活&am…...

Android Crash和ANR监控

文章目录一、Crash1.1 概念1.2 类型二、ANR2.1 概念2.2 类型2.2.1 KeyDispatchTimeout&#xff08;常见&#xff09;2.2.2 BroadcastTimeout2.2.3 ServiceTimeout2.2.4 ContentProviderTimeout三、测试中如何关注3.1 Crash测试关注方法3.2 ANR测试关注方法四、如何记录与处理4.…...

【02 赖世雄英语语法:复句的语法】

复句的语法复句&#xff1a;把单句 连在一起&#xff08;标点符号&#xff0c;连词&#xff0c;关系词&#xff09;1. 连接符号1.1 破折号 — &#xff1a;补充说明&#xff0c;同位语1.2 冒号 : &#xff08;同位语&#xff09;1.3 分号 ; &#xff08; , 连词&#xff09;&am…...

北斗导航 | 多参考一致性监测算法(MRCC)(附伪码)—— B值计算

===================================================== github:https://github.com/MichaelBeechan CSDN:https://blog.csdn.net/u011344545 ===================================================== MRCC 用于接收机失效检查与排除。在进行 MRCC 之前,先判断 4 台接收机…...

数字孪生与 UWB 人员定位:双剑合璧的智能物联新时代

人员定位是指利用各种定位技术对人员在特定场所的位置进行准确定位的技术。人员定位技术主要应用于需要实时监控、管理和保障人员安全的场所&#xff0c;如大型厂区、仓库、医院、学校、商场等。人员定位技术的应用范围非常广泛&#xff0c;例如&#xff1a;-在工厂生产线上&am…...

奇点云DataSimba发版全解析:“企业级”版本升级,提供最佳组合

近日&#xff0c;奇点云发布数据云产品商业化版本的全新升级&#xff1a;DataSimba&#xff08;数据云平台&#xff09;提供极速版、专业版、旗舰版、红旗版&#xff0c;可靠性、可用性、可服务性再进阶&#xff0c;四大版本满足不同企业选择。 「乐高式DIY」or「最佳组合」&am…...

2-7 SpringCloud快速开发入门: Eureka 注册中心高可用集群搭建

接上一章节Eureka 服务注册中心发现与消费服务&#xff0c;这里讲讲Eureka 注册中心高可用集群搭建 Eureka 注册中心高可用集群搭建 Eureka 注册中心高可用集群就是各个注册中心相互注册 Eureka Server的高可用实际上就是将自己作为服务向其他服务注册中心注册自己&#xff0c…...

STL中的函数对象

STL-函数对象 函数对象概念 重载函数调用操作符的类&#xff0c;其对象常称为函数对象函数对象使用重载的()时&#xff0c;行为类似函数调用&#xff0c;也叫仿函数 本质 函数对象(仿函数)是一个类&#xff0c;不是一个函数—修改算法策略—采用虚拟对象调用 函数对象的使用理…...

linux下libevent的编译安装

1&#xff0c;官网下载最新的&#xff0c;https://libevent.org/2&#xff0c;将文件解压&#xff0c;虚拟机可以右键解压&#xff0c;也可以用命令解压&#xff0c;tar zxvf libevent.tar.gz&#xff0c;进行解压缩。这里压缩包的名称只是举例&#xff0c;实际它还会带上版本号…...

深度学习部署笔记(十): CUDA RunTime API-2.2流的学习

1. 流的定义 流&#xff08;Stream&#xff09;是一个基于上下文&#xff08;Context&#xff09;的任务管道抽象&#xff0c;是一组由GPU依次执行的CUDA操作序列&#xff0c;其中每个操作可能会使用或产生数据。在一个上下文中可以创建多个流&#xff0c;每个流都拥有自己的任…...

[ROC-RK3568-PC] [Firefly-Android] 10min带你了解I2C的使用

&#x1f347; 博主主页&#xff1a; 【Systemcall小酒屋】&#x1f347; 博主追寻&#xff1a;热衷于用简单的案例讲述复杂的技术&#xff0c;“假传万卷书&#xff0c;真传一案例”&#xff0c;这是林群院士说过的一句话&#xff0c;另外“成就是最好的老师”&#xff0c;技术…...

工作记录:举步维艰的在线 word 之旅 - tinymce

项目中需要实现 “在线编辑 word 模板” 的功能&#xff0c;我打算使用富文本组件 tinymce &#xff0c;因为业务需求比较特殊&#xff0c;研究一下 tinymce 是否能实现。 如何在 vue 项目中引用 tinymce&#xff0c;可以看另一篇文章 《在 vue 项目中使用 tinymce》 &#x…...

动态规划编译距离

583. 两个字符串的删除操作方法&#xff1a;dp状态表示&#xff1a;以i-1和j-1为结尾的字符串world1和world2&#xff0c;抵达相同的字符串所需的最少操作数属性&#xff1a;最小值状态计算&#xff1a;world1[i-1]和world2[j-1]相同dp[i][j] dp[i-1][j-1];world1[i-1]和world…...

Netty 教程 – 解码器详解

TCP以流的方式进行数据传输&#xff0c;上层的应用为了对消息进行区分&#xff0c;往往采用如下方式 固定消息长度&#xff0c;累计读取到长度和定长LEN的报文后&#xff0c;就认为读取到了个完整的消息&#xff0c;然后将计数器位置重置在读取下一个报文内容将回车换行符作为…...

Allegro如何自动添加测试点操作指导

Allegro如何自动添加测试点操作指导 在做PCB设计的时候,在一些应用场合下需要给PCB上的网络添加测试点,如下图 测试点除了可以手动逐个添加之外,Allegro还支持自动添加测试点,具体操作如下 点击Manufacture点击Testprep...

【CSS】CSS 背景设置 ③ ( 背景位置-长度值设置 | 背景位置-长度值方位值同时设置 )

文章目录一、背景位置-长度值设置二、背景位置-长度值方位值同时设置三、完整代码示例一、背景位置-长度值设置 长度值设置 效果展示 : 设置背景位置为具体值 10px 50px : 粉色区域是盒子的区域 , 图片背景位于盒子位置 x 轴方向 10 像素 , y 轴方向 50 像素 ; 在水平方向上 ,…...

AbTest —— 不同场景下的应用模式

文章目录不同人群眼中的 AbTestAbTest 不同的功能倚重用户关联性弱&#xff0c;经典场景为 Feed - 部门组织形式大多非垂直业务用户关联性强&#xff0c;经典场景为 垂类/工具类APP&#xff1b;部门组织形式大多为垂直业务康为定律-组织决定产品形态不同应用模式下服务构建开机…...

fast-api 一款快速将spring的bean发布成接口并生产对应swagger文档调试的轻量级工具

fast-api简介背景开发痛点:分析需求实战fast-api快速上手1. 引入依赖2. FastApiMapping标记service对象3. swagger2/knife4j 在线测试进阶使用开启调试模式支持指定类或包目录发布如何关闭fast-api自定义fast-api的前缀写在最后简介 fast-api 一款快速将spring的bean(service)发…...

以公益之名 让人类发现数学之美

目录 1.品牌理念高举高打 2.创新赛制 赋能品牌 3.全球化的品牌传播 9月26日&#xff0c;2022阿里巴巴全球数学竞赛获奖名单公布&#xff0c;4座金杯分别由平均年龄25岁&#xff0c;来自美国麻省理工学院、美国布朗大学、北京大学在读数学博士斩获。77位获奖者中00后超五成引热…...

JUC并发编程之HashMap(jdk1.7版本)-底层源码探究

目录 JUC并发编程之HashMap(jdk1.7版本)-底层源码探究 HashMap底层源码 - jdk1.7 基本概念 -采取层层递进&#xff0c;问答式 存储Key-Value的结构 常量和成员变量 构造方法 put方法 inflateTable方法 hash方法 indexFor方法 addEntry方法 resize方法 createEntry…...

QT Q_OBJECT 和 signals/slots

Q_OBJECT宏展开 #define Q_OBJECT \ public: \QT_WARNING_PUSH \Q_OBJECT_NO_OVERRIDE_WARNING \static const QMetaObject staticMetaObject; \virtual const QMetaObject *metaObject() const; \virtual void *qt_metacast(const char *); \virtual int qt_metacall(QMetaOb…...

APM新添加UAVCAN设备

简介 UAVCAN是一种轻量级协议,旨在通过CAN总线在航空航天和机器人应用中实现可靠通信。要实现通信&#xff0c;最基本需要data_type_ id, signature、数据结构、设备程序初始化。 添加设备数据结构文件(.uavcan格式) 1.在以下路径添加设备数据结构文件&#xff0c;根据设备类…...

【C++】string类基本用法

文章目录string类基本用法1. 为什么要学习string类&#xff1f;1.1 C语言中的字符串2. 标准库中的string类2.1 string类2.2 string类的常用接口说明小试牛刀1. 仅仅反转字母2. 字符串中第一个唯一字符3. 字符串中最后一个单词的长度string类基本用法 1. 为什么要学习string类&…...

KDZD耐电压高压击穿强度测试仪

一、技术参数 01、输入电压&#xff1a; 交流 220 V。 02、输出电压&#xff1a; 交流 0--50KV ; 直流 0—50kv 。 03、电器容量&#xff1a;3KVA。 04、高压分级&#xff1a;0—50KV&#xff0c;&#xff08;全程可调&#xff09;。 05、升压速率&#xff1a;0.1KV/s-…...

数组和指针面试题的补充(细的抠jio)

生命是一条艰险的峡谷&#xff0c;只有勇敢的人才能通过。 ——米歇潘 说明&#xff1a;用的vs都是x86的环境&#xff0c;也就是32位平台。 建议&#xff1a;对于难题来说&#xff0c;一定要配合画图来解决问题。 第一题&#xff1a; #include<stdio.h> int…...

Java多线程基础

文章目录Java多线程基础一、什么是进程与线程&#xff1f;二、线程和进程的区别【重点】三、线程的创建方式【重点】1. 继承Thread类2. 实现Runnable接口3. lambda 表达式四、Thread的常见属性线程中断自己定义一个标志位Thread类提供的静态方法线程的状态Java多线程基础 一、…...