1. JDBC与MyBatis技术全景解析
作为Java开发者绕不开的两大持久层技术,JDBC和MyBatis的关系就像手动挡与自动挡汽车的区别。2003年我第一次用JDBC写学生管理系统时,光是一个分页查询就写了200行模板代码,而十年后使用MyBatis实现同样功能只需15行配置。这种开发效率的跃迁背后,是ORM框架对原生JDBC的创造性封装。
2. 核心架构对比
2.1 JDBC的"机械传动"模型
JDBC的经典四步曲就像老式收音机的调频旋钮:
java复制// 1. 加载驱动(Class.forName这行代码在JDBC 4.0后已可省略)
Class.forName("com.mysql.cj.jdbc.Driver");
// 2. 建立连接(注意要放在try-with-resources中自动关闭)
try (Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/test?useSSL=false",
"root",
"password")) {
// 3. 创建Statement(PreparedStatement更安全)
String sql = "SELECT * FROM users WHERE age > ?";
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setInt(1, 18);
// 4. 处理结果集
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
System.out.println(rs.getString("username"));
}
}
}
}
这种模式的问题在于:
- 资源管理繁琐(需要手动关闭Connection/Statement/ResultSet)
- SQL与Java代码强耦合
- 结果集到对象的转换需要手工完成
- 没有连接池等性能优化机制
2.2 MyBatis的"自动变速箱"设计
MyBatis通过三个核心组件重构了持久层:
- SqlSessionFactory:相当于数据库连接工厂
- Mapper接口:声明CRUD方法
- XML映射文件:解耦SQL与Java代码
典型配置示例:
xml复制<!-- mybatis-config.xml -->
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/example/mapper/UserMapper.xml"/>
</mappers>
</configuration>
3. 深度功能对比
3.1 事务管理机制
JDBC的原始事务控制:
java复制Connection conn = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false); // 开启事务
// 执行多个SQL操作
updateAccount(conn, fromId, -amount);
updateAccount(conn, toId, amount);
conn.commit(); // 提交事务
} catch (SQLException e) {
if (conn != null) conn.rollback(); // 回滚
} finally {
if (conn != null) conn.close();
}
MyBatis通过注解简化事务:
java复制@Transactional
public void transferMoney(int fromId, int toId, int amount) {
accountMapper.deduct(fromId, amount);
accountMapper.add(toId, amount);
}
3.2 动态SQL构建
JDBC需要手动拼接SQL字符串:
java复制StringBuilder sql = new StringBuilder("SELECT * FROM products WHERE 1=1 ");
if (minPrice != null) {
sql.append("AND price >= ").append(minPrice);
}
if (category != null) {
sql.append("AND category = '").append(category).append("'");
}
// 存在SQL注入风险
MyBatis提供安全的动态SQL标签:
xml复制<select id="searchProducts" resultType="Product">
SELECT * FROM products
<where>
<if test="minPrice != null">
AND price >= #{minPrice}
</if>
<if test="category != null">
AND category = #{category}
</if>
</where>
</select>
4. 性能优化实践
4.1 连接池配置对比
JDBC需要第三方连接池(如HikariCP):
java复制HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost/test");
config.setUsername("root");
config.setPassword("password");
config.setMaximumPoolSize(20);
HikariDataSource ds = new HikariDataSource(config);
MyBatis内置连接池配置:
xml复制<dataSource type="POOLED">
<property name="poolMaximumActiveConnections" value="20"/>
<property name="poolMaximumIdleConnections" value="10"/>
<property name="poolMaximumCheckoutTime" value="20000"/>
</dataSource>
4.2 批量操作效率
JDBC批量插入:
java复制try (PreparedStatement stmt = conn.prepareStatement(
"INSERT INTO users (name,age) VALUES (?,?)")) {
for (User user : userList) {
stmt.setString(1, user.getName());
stmt.setInt(2, user.getAge());
stmt.addBatch();
if (i % 1000 == 0) {
stmt.executeBatch();
}
}
stmt.executeBatch();
}
MyBatis批量操作:
java复制try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
UserMapper mapper = session.getMapper(UserMapper.class);
for (User user : userList) {
mapper.insert(user);
}
session.commit();
}
5. 实战避坑指南
5.1 结果映射的"幽灵字段"问题
当数据库字段名与Java属性名不一致时:
java复制public class User {
private String userName; // 数据库字段是user_name
private Integer userAge; // 数据库字段是age
}
解决方案1:SQL别名
xml复制<select id="getUsers" resultType="User">
SELECT
user_name AS userName,
age AS userAge
FROM users
</select>
解决方案2:结果映射
xml复制<resultMap id="userResultMap" type="User">
<result property="userName" column="user_name"/>
<result property="userAge" column="age"/>
</resultMap>
5.2 N+1查询问题
错误示范:
xml复制<select id="getBlog" resultType="Blog">
SELECT * FROM blog WHERE id = #{id}
</select>
<select id="getComments" resultType="Comment">
SELECT * FROM comment WHERE blog_id = #{blogId}
</select>
优化方案1:联合查询
xml复制<select id="getBlogWithComments" resultMap="blogResultMap">
SELECT
b.*, c.id as comment_id, c.content
FROM blog b
LEFT JOIN comment c ON b.id = c.blog_id
WHERE b.id = #{id}
</select>
优化方案2:嵌套查询(需开启懒加载)
xml复制<resultMap id="blogResultMap" type="Blog">
<collection
property="comments"
column="id"
select="getCommentsByBlogId"
fetchType="lazy"/>
</resultMap>
6. 混合开发模式
6.1 在MyBatis中使用原生JDBC
通过SqlSession获取Connection:
java复制try (SqlSession session = sqlSessionFactory.openSession()) {
Connection conn = session.getConnection();
// 执行原生JDBC操作
DatabaseMetaData meta = conn.getMetaData();
System.out.println(meta.getDatabaseProductName());
}
6.2 动态切换数据源
基于AbstractRoutingDataSource实现:
java复制public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DatabaseContextHolder.get();
}
}
// 使用示例
DatabaseContextHolder.set("slaveDB");
userMapper.queryUserById(1);
DatabaseContextHolder.clear();
7. 扩展能力对比
7.1 插件开发
MyBatis拦截器示例(实现分页):
java复制@Intercepts(@Signature(
type= Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class PageInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 解析分页参数
// 修改原始SQL
// 执行count查询
// 返回分页结果
}
}
7.2 类型处理器
自定义JSON类型处理:
java复制public class JsonTypeHandler<T> extends BaseTypeHandler<T> {
private Class<T> clazz;
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
T parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, JSON.toJSONString(parameter));
}
@Override
public T getNullableResult(ResultSet rs, String columnName)
throws SQLException {
String json = rs.getString(columnName);
return JSON.parseObject(json, clazz);
}
}
8. 现代演进路线
8.1 MyBatis-Plus的增强
Lambda表达式查询:
java复制List<User> users = userMapper.selectList(
Wrappers.<User>lambdaQuery()
.gt(User::getAge, 18)
.likeRight(User::getName, "张")
);
8.2 响应式编程支持
MyBatis+R2DBC示例:
java复制@Repository
public interface UserRepository {
@Query("SELECT * FROM users WHERE age > :age")
Flux<User> findByAgeGreaterThan(int age);
}
在微服务架构下,我通常会这样选择技术组合:
- 简单CRUD:MyBatis-Plus + HikariCP
- 复杂查询:MyBatis动态SQL + PageHelper
- 超高并发:MyBatis二级缓存 + Redis
- 异步场景:R2DBC响应式驱动