# 开发 Java 应用程序的最佳实践

__*JDBC 最佳实践*__

+ JDBC
  + 推荐使用 **Prepared Statement** 
  + 推荐使用 **addBatch/executeBatch**
  + 对于查询: `fetchSize="-2147483648"`
  + 对于批处理: `rewriteBatchedStatements=true`
  + 对于大型结果集:
    + 设置 **FetchSize** 为 **Integer.MIN_VALUE** 确保客户端不进行缓存
    + 客户端将通过以下方式读取来自网络连接的执行结果: `StreamingResult` 
+ 使用连接池
  + 例如: `HikariCP, tomcat-jdbc, Durid, c3p0, dbcp` 
+ 数据访问框架, 以 **MyBatis** 为例:
  + MyBatis 与 TiDB 高度兼容
    + 在 **Mapper** 里使用 **#{param}** 而不是 **${param}**  
    + 对于批处理操作,把会话的 **ExecutorType** 设置为 **Batch**
-------------------------------------------------------------------

1. JDBC
1.1 对于 OLTP 场景,程序发送给数据库的 SQL 语句在去除参数变化后都是可穷举的某几类,因此建议使用预处理语句 (Prepared Statements) 代替普通的文本执行,并复用预处理语句来直接执行,从而避免 TiDB 重复解析和生成 SQL 执行计划的开销。

1.2 通过JDBC执行sql语句时,update和delete执行sql的语句是一条一条发往数据库执行。
但是！数据库的处理速度是很快,单次吞吐量是很大,执行效率极高。这时,"冲突"就发生,数据库处理速度很快,但是执行发往数据库要执行的语句却是一条一条发送,其速度可想而知。所以这种情况数据库的处理速度极快的特点就无法体现。所以,为了解决这种"冲突",**addBatch()**出现了！addBatch()是把若干sql语句装载到一起,然后一次性传送到数据库执行,即是批量处理sql数据的。所以"冲突"得到解决,执行时间大大缩短。

1.3 防止出现隐式数据类型转换,一般如果等号左边事字符型的时候,要格外注意这个问题,否则无法走索引了。

1.4 在进行 batch 写入处理时推荐配置 rewriteBatchedStatements = true,在已经使用 addBatch 或 executeBatch 后默认 JDBC 还是会一条条 SQL 发送。

1.5 一般情况下,为提升执行效率,JDBC 会默认提前获取查询结果并将其保存在客户端内存中。但在查询返回超大结果集的场景中,客户端会希望数据库服务器减少向客户端一次返回的记录数,等客户端在有限内存处理完一部分后再去向服务器要下一批。
在 JDBC 中通常有以下两种处理方式:
（1）设置 FetchSize 为 Integer.MIN_VALUE 让客户端不缓存,客户端通过 StreamingResult 的方式从网络连接上流式读取执行结果。
（2）使用 Cursor Fetch,首先需设置 FetchSize 为正整数,且在 JDBC URL 中配置 useCursorFetch = true。
TiDB 中同时支持两种方式,但更推荐使用第一种将 FetchSize 设置为 Integer.MIN_VALUE 的方式,比第二种功能实现更简单且执行效率更高。
我这这里,推荐的是FetchSize 为 Integer.MIN_VALUE 让客户端不缓存！原因是防止 OOM 现象的发生。

2.1 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个；释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。 这项技术能明显提高对数据库操作的性能。

3.1
MyBatis 的 Mapper 中支持两种参数:
select 1 from t where id = #{param1} 会作为预处理语句,被转换为 select 1 from t where id = ? 进行预处理,并使用实际参数来复用执行,通过配合前面的 Prepare 连接参数能获得最佳性能。
select 1 from t where id = ${param2} 会做文本替换为 select 1 from t where id = 1 执行,如果这条语句被预处理为不同参数,可能会导致 TiDB 缓存大量的预处理语句,并且以这种方式执行 SQL 有注入安全风险。

3.2
在 openSession 的时候可以选择 ExecutorType,MyBatis 支持三种 executor:
Simple:每次执行都会向 JDBC 进行预处理语句的调用（如果 JDBC 配置有开启 cachePrepStmts,重复的预处理语句会复用）。
Reuse:在 executor 中缓存预处理语句,这样不用 JDBC 的 cachePrepStmts 也能减少重复预处理语句的调用。
Batch:每次更新只有在 addBatch 到 query 或 commit 时才会调用 executeBatch 执行,如果 JDBC 层开启了 rewriteBatchStatements,则会尝试改写,没有开启则会一条条发送。

通常默认值是 Simple,需要在调用 openSession 时改变 ExecutorType。如果是 Batch 执行,会遇到事务中前面的 update 或 insert 都非常快,而在读数据或 commit 事务时比较慢的情况,这实际上是正常的,在排查慢 SQL 时需要注意。

这里我们推荐使用 Batch,它可以改写具备批处理条件的 SQL 语句。