Mybatis开启驼峰命名映射、Mybatis数据库与实体类自动映射、Mybatis底层映射原理、源码讲解结果集映射为实体类原理

一、前言

本篇讲解mybatis中如何将数据库中带下划线的字段user_name自动映射为实体类中驼峰命名的字段userName,以及Mybatis底层映射原理。

仔细阅读本篇文章,你将弄懂以下疑问:

  1. 数据库字段与实体类字段是如何匹配上的,如数据库user_name对应实体类userName
  2. 当数据库字段类型与实体类属性不匹配时如何转换的,如数据库int怎么转换成实体类String

二、开启自动驼峰命名转换

将mapUnderscoreToCamelCase配置为true即可自动将数据库中的user_name转换为实体类中的userName

方式一: 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>  
    <settings>  
        <setting name="mapUnderscoreToCamelCase" value="true" />  
    </settings>  
</configuration>

方式二: application.properties文件中开启配置

mybatis.configuration.mapUnderscoreToCamelCase=true
或
mybatis.configuration.map-underscore-to-camel-case=true

方式三: yml中开启配置

# Mybatis开启驼峰映射
mybatis:
  configuration:
    mapUnderscoreToCamelCase: true

方式四: SpringBoot中还可以使用自定义配置类的方式配置;给容器中添加一个ConfigurationCustomizer;

@Configuration
public class MyBatisConfig {

    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return new ConfigurationCustomizer() {

            @Override
            public void customize(org.apache.ibatis.session.Configuration configuration) {
                configuration.setMapUnderscoreToCamelCase(true);
            }
        };
    }
}

三、案例讲解下划线映射为驼峰原理

新建dto实体类

/**
 * @author 
 */
@Data
public class TestDto {
    String aabb;
    String user;
    String userName;
}

mapper层查询并返回dto类

public interface TestMapper {
	
	@Select("select '1' as aa_BB,'2' as user, '3' as user_name")
	TestDto test();
}
相当于mapper.xml文件中的
<select id="test" resultType="com.demo.TestDto">
	select '1' as aa_BB,'2' as user, '3' as user_name
</select>

3.1、自动映射原理

1.mybatis会根据返回实体类维护一个map集合,key为实体类属性大写,value为实体类中的属性。

Map集合
AABB aabb;
USER user;
USERNAME userName;

2.若mapUnderscoreToCamelCase=true ,则会把sql中的下划线全部去除,然后转换为大写后去map中匹配,若命中则将值赋予对应的属性

四、自动映射的坑

4.1、mapUnderscoreToCamelCase=true的坑

由于mapUnderscoreToCamelCase=true是把下划线去除,然后转大写跟实体类进行匹配,所以数据库中aab_b也能命中实体类中的aaBb

五、源码讲解结果集映射为实体类原理

此处以mybatis-3.5.6的源码结合druid-1.1.16、mysql为例,讲解mybatis入参映射以及mybatis中resultType配合mapUnderscoreToCamelCase=true实现出参自动映射原理
在这里插入图片描述
在这里插入图片描述
看上面图片实体类类型,aaBb与数据库中字段名称不一致且类型也不一致,id是int类型但是在mapper中传入了String类型,实体类中userName是驼峰模式表中是user_name。那么mybtis是如何实现入参映射,以及sql执行后的出参映射呢?

首先进行执行
在这里插入图片描述
底层会调用MapperProxy中的invoke方法
在这里插入图片描述
下面跳过缓存,直接进入SimpleExecutor的doQuery里面的stmt = prepareStatement方法,此方法是进行 sql的占位符拼接。prepareStatement方法执行结束后,执行handler.query(stmt,resultHandler)方法,这个方法是执行sql,以及sql和实体类的映射。
在这里插入图片描述

引申说明:
上图boundSql中的sql及parameterMappings是在项目启动时就加载出来的,项目启动时底层会解析mapper.xml文件,其中javaType对应mapper.xml中的parameterType属性,jdbcType对应#{id,jdbcType=INTEGER}

我们先看一下这里的handler对象,这里创建了一个RoutingStatementHandler对象,然后赋值对象内的delegate属性为PreparedStatementHandler
在这里插入图片描述

扩展引申:
SimpleStatementHandler 和 PreparedStatementHandler 的区别是 SQL 语句是否包含变量,是否通过外部进行参数传入。SimpleStatementHandler 用于执行没有任何参数传入的 SQL,PreparedStatementHandler 需要对外部传入的变量和参数进行提前参数绑定和赋值。

  • SimpleStatementHandler: 管理 Statement 对象并向数据库中推送不需要预编译的SQL语句。
  • PreparedStatementHandler: 管理 Statement 对象并向数据中推送需要预编译的SQL语句。
  • CallableStatementHandler:管理 Statement 对象并调用数据库中的存储过程。

我们看一下prepareStatement方法,看看入参是怎么映射的,为什么jdbcType=INTEGER,但是传入了String类型也可以。
下面stmt = prepareStatement方法
在这里插入图片描述
下面是重点,是参数的处理。以及stme:PreparedStatementProxyImpl类去设置sql。
在这里插入图片描述
经过SimpleExecutor.prepareStatement方法—>PreparedStatementHandler.parameterize方法,最后调用了DefaultParameterHandler.parameterize方法
在这里插入图片描述
在这里插入图片描述
继续进入发现最后调用了StringTypeHandler完成了参数的映射,jdbcType没有用到,之后就是PreparedStatementProxyImpl去setString(1,”1”)。占位符设置。
在这里插入图片描述
可以发现直到sql占位符结束都是String类型,所以,Mybatis不会对入参的数据进行类型转换,只会去映射对应sql的Varcher类型。但是sql的那一列类型是INT类型,不过依然能查出来,因为Mysql里面有隐式转换。但是有时候转换的话,sql列的数据类型发生变化会造成索引失效。

执行handler.query(stmt,resultHandler)方法
在这里插入图片描述
在这里插入图片描述
上面我们已经讲过了,本篇文章的示例创建的delegate对象是PreparedStatementHandler,所以delegate.query会调用PreparedStatementHandler中的query方法。

ps .execute()先去执行,之后进行映射实体类。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

然后执行preparedStatement_execute
在这里插入图片描述
之后执行chain.preparedStatement_execute();
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上面执行结束后,所有的结果就已经查出来了。结果集开始映射实体类
在这里插入图片描述

public List<Object> handleResultSets(Statement stmt) throws SQLException {
	ErrorContext.instance().activity("handling results").object(this.mappedStatement.getId());
	// 用于记录每个ResultSet映射出来的Java对象
	List<Object> multipleResults = new ArrayList();
	int resultSetCount = 0;
	// 从Statement中获取第一个ResultSet,其中对不同的数据库有兼容处理逻辑,
    // 这里拿到的ResultSet会被封装成ResultSetWrapper对象返回
	ResultSetWrapper rsw = this.getFirstResultSet(stmt);
	// 获取这条SQL语句关联的全部ResultMap规则。如果一条SQL语句能够产生多个ResultSet,
    // 那么在编写Mapper.xml映射文件的时候,我们可以在SQL标签的resultMap属性中配置多个
    // <resultMap>标签的id,它们之间通过","分隔,实现对多个结果集的映射
	List<ResultMap> resultMaps = this.mappedStatement.getResultMaps();
	int resultMapCount = resultMaps.size();
	this.validateResultMapsCount(rsw, resultMapCount);

	while(rsw != null && resultMapCount > resultSetCount) { // 遍历ResultMap集合
		ResultMap resultMap = (ResultMap)resultMaps.get(resultSetCount);
		// 根据ResultMap中定义的映射规则处理ResultSet,并将映射得到的Java对象添加到
	    // multipleResults集合中保存
		this.handleResultSet(rsw, resultMap, multipleResults, (ResultMapping)null);
		//获取下一个resultSet
		rsw = this.getNextResultSet(stmt);
		 // 清理nestedResultObjects集合,这个集合是用来存储中间数据的
		this.cleanUpAfterHandlingResultSet();
		// 递增ResultSet编号
		++resultSetCount;
	}
	// 下面这段逻辑是根据ResultSet的名称处理嵌套映射
	String[] resultSets = this.mappedStatement.getResultSets();
	if (resultSets != null) {
		while(rsw != null && resultSetCount < resultSets.length) {
			ResultMapping parentMapping = (ResultMapping)this.nextResultMaps.get(resultSets[resultSetCount]);
			if (parentMapping != null) {
				String nestedResultMapId = parentMapping.getNestedResultMapId();
				ResultMap resultMap = this.configuration.getResultMap(nestedResultMapId);
				this.handleResultSet(rsw, resultMap, (List)null, parentMapping);
			}

			rsw = this.getNextResultSet(stmt);
			this.cleanUpAfterHandlingResultSet();
			++resultSetCount;
		}
	}
	// 返回全部映射得到的Java对象
	return this.collapseSingleResultList(multipleResults);
}

ResultSetWrapper 主要用于封装 ResultSet 的一些元数据,其中记录了 ResultSet 中每列的名称、对应的 Java 类型、JdbcType 类型以及每列对应的 TypeHandler。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里我们重点来看 handleRowValuesForSimpleResultMap() 方法如何映射一个 ResultSet 的,该方法的核心步骤可总结为如下。

private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    DefaultResultContext<Object> resultContext = new DefaultResultContext();
    ResultSet resultSet = rsw.getResultSet();
    //跳过RowBounds设置的offset值
    this.skipRows(resultSet, rowBounds);
    //判断数据是否小于limit,如果小于limit的话就不断的循环取值
    while(this.shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
        ResultMap discriminatedResultMap = this.resolveDiscriminatedResultMap(resultSet, resultMap, (String)null);
        //在此处完成结果集与实体类的映射
        Object rowValue = this.getRowValue(rsw, discriminatedResultMap, (String)null);
        this.storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
    }

}

private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) throws SQLException {
	//判断数据是否小于limit,小于返回true
    return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
}

//跳过不需要的行,应该就是rowbounds设置的limit和offset
private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
	if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
	  if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
		rs.absolute(rowBounds.getOffset());
	  }
	} else {
	  //跳过RowBounds中设置的offset条数据,只能逐条滚动到指定位置
	  for (int i = 0; i < rowBounds.getOffset(); i++) {
		rs.next();
	  }
	}
}

1.执行 skipRows() 方法跳过多余的记录,定位到指定的行。
2.通过 shouldProcessMoreRows() 方法,检测是否还有需要映射的数据记录。
3.如果存在需要映射的记录,则先通过 resolveDiscriminatedResultMap() 方法处理映射中用到的 Discriminator,决定此次映射实际使用的 ResultMap。
4.通过 getRowValue() 方法对 ResultSet 中的一行记录进行映射,映射规则使用的就是步骤 3 中确定的 ResultMap。
5.执行 storeObject() 方法记录步骤 4 中返回的、映射好的 Java 对象。

在这里插入图片描述
当前方法去执行映射的applyAutomaticMappings()去了。执行结束就映射完成了。返回rowValue结果。
在这里插入图片描述
下面执行applyAutomaticMappings(),此方法作用是将sql返回值映射到实体类。
在这里插入图片描述
createAutomaticMappings方法返回结果集与实体类的映射关系。即维护数据库aab_b字段对应实体类aaBb字段,user_name对应userName;

在这里插入图片描述
在这里插入图片描述

然后就是根据数据库与实体类的映射关系循环设置值
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
当所有字段都赋值完毕后返回结果
在这里插入图片描述
总结:mybatis是根据java的入参类型进行sql查询的,(最好规范使用),如当前传入的是一个String类型的参数,那么sql里面就是Varcher类型,但是数据库里面的如果是int类型,有可能造成当前列的索引失效(隐式转换)。sql执行后返回结果后的时候,返回的是数据库类型,但是映射到实体类的时候,mybatis会自动给转成需要的类型,如返回的是int类型,但是实体类是String类型,那么就会自动转成String类型映射到实体类。(mybatis入参不管,出参管)



六、总结

通过本篇博文,我们可以得出以下结论。

  1. 入参时只跟parameterType属性有关,jdbcType用不上,所以当两者类型冲突时也可以拼接sql, 只是最后执行时可能会有问题。

UserMapper.java

public interface UserMapper {

    User selectById(String id);
}

UserMapper.xml

<select id="selectById" resultType="com.example.demo.entity.User"
            parameterType="java.lang.String">
  select id,user_name,aab_b from user where id = #{id,jdbcType=INTEGER}
</select>








参考文章:https://learn.lianglianglee.com/%E4%B8%93%E6%A0%8F/%E6%B7%B1%E5%85%A5%E5%89%96%E6%9E%90%20MyBatis%20%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86-%E5%AE%8C/14%20%20%E6%8E%A2%E7%A9%B6%20MyBatis%20%E7%BB%93%E6%9E%9C%E9%9B%86%E6%98%A0%E5%B0%84%E6%9C%BA%E5%88%B6%E8%83%8C%E5%90%8E%E7%9A%84%E7%A7%98%E5%AF%86%EF%BC%88%E4%B8%8A%EF%BC%89.md
https://blog.csdn.net/m0_65789764/article/details/130795359

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: MyBatis是一种支持多种数据库的ORM(对象关系映射)框架。它通过映射文件(XML或注解)将实体类数据库表进行对应,实现了实体类数据库表之间的映射关系。映射文件中可以定义SQL语句,通过SQL语句操作数据库,将查询结果映射实体类或者合对象返回。在MyBatis中,通过配置数据源来连接数据库,以及配置事务来保证数据的一致性。 ### 回答2: Mybatis是一款优秀的ORM(对象关系映射)框架,通过配置文件的方式将数据库表的字段映射为Java中的实体类属性。它的映射规则非常灵活,可以根据不同的需求进行配置。 在Mybatis中,我们可以通过注解或XML配置文件的方式进行实体类数据库字段的映射。首先,我们需要定义一个Java实体类,其中的属性名与数据库表字段一一对应,类型也要匹配。例如: ```java public class User { private int id; private String name; private int age; // getter和setter方法 } ``` 然后,在Mapper接口中定义与数据库操作相关的方法。在使用Mapper接口的方法时,Mybatis自动数据库结果映射为Java对象,同时也会将Java对象转换为支持的数据类型进行数据库操作。例如: ```java public interface UserMapper { // 插入用户数据到数据库 void insert(User user); // 根据id查询用户数据 User selectById(int id); // 更新用户数据到数据库 void update(User user); // 删除用户数据 void delete(int id); } ``` 在映射实体类属性和数据库表字段的过程中,我们可以采用不同的方式,如: 1. 使用注解方式,直接在实体类的属性上添加注解,表示该属性对应数据库表的字段,例如: ```java public class User { @Id private int id; @Column(name = "user_name") private String name; @Column(name = "user_age") private int age; // getter和setter方法 } ``` 2. 使用XML文件配置,将实体类数据库表字段进行映射,例如: ```xml <resultMap id="userMap" type="User"> <id column="id" property="id"/> <result column="name" property="name"/> <result column="age" property="age"/> </resultMap> ``` 无论采用哪种方式,都需要注意属性名与表字段名的大小写保持一致,否则可能无法映射成功。 总的来说,Mybatis提供了非常灵活的实体类数据库字段映射方式,可以根据不同的业务需求进行配置,使得数据访问层的开发变得更加简单、高效。 ### 回答3: MyBatis 是一个支持自定义 SQL、存储过程和高级映射的持久层框架。在 MyBatis 中,数据库表中的字段与 Java 实体类中的属性需要进行映射,以方便进行数据库操作。具体来说,MyBatis提供了三种方式实现映射关系: 1. 基于注解的映射 这种方式通过在 Java 实体类中添加注解,来实现数据库字段与实体类属性的映射关系。例如: ``` public class User { @Id private Long id; @Column(name = "user_name") private String userName; // getter and setter } ``` @Id 注解表示该属性为主键,@Column 注解表示该属性对应数据库表中的哪个字段。当进行数据库操作时,MyBatis自动实体类属性映射到 SQL 语句中,例如: ``` <select id="getUserById" resultType="User"> SELECT * FROM user WHERE id = #{id} </select> ``` 在执行这个 SQL 语句时,MyBatis 会将 #{id} 替换为 User 对象中的 id 属性,同时将查询结果映射为 User 对象。 2. 基于 XML 文件的映射 除了使用注解来实现映射关系,MyBatis 还支持使用 XML 文件来进行配置。在 XML 文件中,可以指定数据库表中的字段名、实体类的属性名以及它们之间的映射关系。例如: ``` <mapper namespace="com.example.mapper.UserMapper"> <resultMap id="userMap" type="User"> <id property="id" column="id" /> <result property="userName" column="user_name" /> </resultMap> <select id="getUserById" resultMap="userMap"> SELECT * FROM user WHERE id = #{id} </select> </mapper> ``` 在这个 XML 文件中,通过 resultMap 标签声明了一个映射关系,它可以将 SQL 查询结果映射为 User 对象。使用 select 标签指定 SQL 语句以及要使用的 resultMap,完成数据库查询和实体类映射的工作。 3. 基于 Java 代码的映射 第三种方式是使用 Java 代码手动实现实体类数据库表的映射关系。在这种方式下,需要编写一个继承自 org.apache.ibatis.type.BaseTypeHandler 类的类,并覆盖其中的方法,以实现数据库字段与实体类属性的映射。例如: ``` @MappedTypes(User.class) public class UserHandler extends BaseTypeHandler<User> { @Override public void setNonNullParameter(PreparedStatement ps, int i, User parameter, JdbcType jdbcType) throws SQLException { ps.setLong(i, parameter.getId()); } @Override public User getNullableResult(ResultSet rs, String columnName) throws SQLException { User user = new User(); user.setId(rs.getLong(columnName)); return user; } @Override public User getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { User user = new User(); user.setId(cs.getLong(columnIndex)); return user; } } ``` 在这个代码中,我们覆盖了 BaseTypeHandler 中的三个方法,以实现 Java 对象和数据库表中字段的互相转换。通过在配置文件中注册该类型处理器,就可以完成数据库字段和实体类属性的映射。 总之,MyBatis 提供了多种方式实现数据库字段和实体类属性的映射关系,开发者可以根据实际需要,选择最适合自己的方式来进行开发。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值