作为Java开发者,数据库操作是必备技能。记得我刚入行时,面对JDBC那一堆样板代码简直头疼不已,直到后来接触了MyBatis才真正体会到开发效率的提升。今天我就结合自己多年的实战经验,带大家系统掌握从原生JDBC到MyBatis的完整知识体系。
JDBC(Java Database Connectivity)是Java官方提供的一套操作关系型数据库的API规范。它的核心价值在于:
典型JDBC操作流程(以MySQL为例):
java复制// 1. 加载驱动(新版JDBC可省略此步骤)
Class.forName("com.mysql.cj.jdbc.Driver");
// 2. 建立连接
String url = "jdbc:mysql://localhost:3306/db?useSSL=false&serverTimezone=UTC";
Connection conn = DriverManager.getConnection(url, "user", "password");
// 3. 创建Statement
Statement stmt = conn.createStatement();
// 4. 执行SQL
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
// 5. 处理结果集
while(rs.next()) {
System.out.println(rs.getString("username"));
}
// 6. 释放资源
rs.close();
stmt.close();
conn.close();
重要提示:实际项目中务必使用try-with-resources确保资源释放,避免内存泄漏
直接使用Statement存在SQL注入风险,应该始终优先使用PreparedStatement:
java复制String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, username);
pstmt.setString(2, password);
ResultSet rs = pstmt.executeQuery();
预编译SQL的三大优势:
ResultSet的常见陷阱及解决方案:
java复制// 错误示范:列索引从0开始
int id = rs.getInt(0);
// 正确做法:列索引从1开始,或使用列名
int id = rs.getInt(1);
String name = rs.getString("username");
// 处理NULL值
int age = rs.getInt("age");
if(rs.wasNull()) {
age = -1; // 自定义默认值
}
MyBatis的核心组件关系图:
code复制SqlSessionFactoryBuilder → SqlSessionFactory → SqlSession → Mapper
配置初始化流程:
注解方式适合简单SQL:
java复制@Select("SELECT * FROM users WHERE id = #{id}")
User getUserById(@Param("id") int id);
XML方式更适合复杂场景:
xml复制<select id="getComplexUser" resultType="User">
SELECT u.*, d.department_name
FROM users u
JOIN departments d ON u.dept_id = d.id
<where>
<if test="name != null">
AND u.username LIKE CONCAT('%', #{name}, '%')
</if>
<if test="minAge != null">
AND u.age >= #{minAge}
</if>
</where>
ORDER BY ${orderBy}
</select>
MyBatis提供了强大的动态SQL能力:
xml复制<!-- 条件分支 -->
<choose>
<when test="role == 'admin'">
AND access_level = 3
</when>
<when test="role == 'manager'">
AND access_level = 2
</when>
<otherwise>
AND access_level = 1
</otherwise>
</choose>
<!-- 批量插入 -->
<insert id="batchInsert">
INSERT INTO users (username, email) VALUES
<foreach collection="list" item="user" separator=",">
(#{user.name}, #{user.email})
</foreach>
</insert>
以Druid为例的推荐配置:
yaml复制spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/db
username: root
password: 123456
# 关键性能参数
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
一级缓存(默认开启):
二级缓存(需显式配置):
xml复制<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
注意:分布式环境下建议使用Redis等集中式缓存替代二级缓存
JDBC方式:
java复制connection.setAutoCommit(false);
PreparedStatement pstmt = connection.prepareStatement(sql);
for (User user : users) {
pstmt.setString(1, user.getName());
pstmt.addBatch();
}
pstmt.executeBatch();
connection.commit();
MyBatis方式:
java复制try(SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
UserMapper mapper = session.getMapper(UserMapper.class);
for (User user : users) {
mapper.insert(user);
}
session.commit();
}
典型问题1:连接泄漏
症状:应用运行一段时间后无法获取新连接
解决方案:
典型问题2:慢SQL
诊断方法:
xml复制<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
优化方案:
物理分页(推荐):
xml复制<select id="selectPage" resultType="User">
SELECT * FROM users
ORDER BY id
LIMIT #{offset}, #{pageSize}
</select>
逻辑分页(小数据量):
java复制RowBounds rowBounds = new RowBounds(offset, pageSize);
List<User> users = sqlSession.selectList("selectUsers", null, rowBounds);
自定义类型处理器示例:
java复制@MappedTypes(PhoneNumber.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class PhoneTypeHandler extends BaseTypeHandler<PhoneNumber> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
PhoneNumber parameter, JdbcType jdbcType) {
ps.setString(i, parameter.getValue());
}
// 其他方法实现...
}
注册自定义处理器:
xml复制<typeHandlers>
<typeHandler handler="com.example.PhoneTypeHandler"/>
</typeHandlers>
Spring Boot自动配置的关键步骤:
java复制@Configuration
@MapperScan(basePackages = "com.primary.mapper",
sqlSessionFactoryRef = "primarySqlSessionFactory")
public class PrimaryDataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public SqlSessionFactory primarySqlSessionFactory() throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(primaryDataSource());
factory.setMapperLocations(
new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/primary/*.xml"));
return factory.getObject();
}
}
集成Druid监控:
java复制@Bean
public ServletRegistrationBean<StatViewServlet> druidServlet() {
ServletRegistrationBean<StatViewServlet> reg = new ServletRegistrationBean<>();
reg.setServlet(new StatViewServlet());
reg.addUrlMappings("/druid/*");
// 添加控制台管理用户
reg.addInitParameter("loginUsername", "admin");
reg.addInitParameter("loginPassword", "admin");
return reg;
}
实现分页插件示例:
java复制@Intercepts(@Signature(type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class}))
public class PaginationInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler handler = (StatementHandler) invocation.getTarget();
// 解析分页参数并重写SQL
// ...
return invocation.proceed();
}
}
注册插件:
xml复制<plugins>
<plugin interceptor="com.example.PaginationInterceptor"/>
</plugins>
基于MyBatis的租户隔离方案:
java复制public class TenantInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
BoundSql boundSql = ((StatementHandler)invocation.getTarget()).getBoundSql();
String newSql = boundSql.getSql() + " AND tenant_id = " + getCurrentTenant();
resetSql(invocation, newSql);
return invocation.proceed();
}
// 其他实现细节...
}
XML配置方式:
xml复制<select id="callProcedure" statementType="CALLABLE">
{call sp_get_user_details(
#{id,mode=IN},
#{name,mode=OUT,jdbcType=VARCHAR}
)}
</select>
Java调用方式:
java复制Map<String, Object> params = new HashMap<>();
params.put("id", 123);
mapper.callProcedure(params);
String name = (String) params.get("name");
掌握这些核心知识和实战技巧后,相信你在日常开发中能够更加游刃有余地处理各种数据库操作场景。记住,好的ORM框架应该成为助力而不是束缚,理解底层原理才能更好地发挥框架价值。