背景
周五的时候,我的主管把我叫过去说有一个需求很急,采用传统的开发模式可能来不及,他说他打算开发一个框架来快速集成,当然把我叫过去的目的是和他一起开发,他给我的任务是,让我开发一个轻量级的mybatis,大致要求如下两点:
- 不要xml文件
- 要有缓存
老实讲我之前是没有开发过框架的,并且mybatis我虽然用的是很溜,但是源码我确实没有看过,而且只有一天时间,难度对我来说是挺大的。
于是我继续和我的老大沟通,他具体是要干什么,他说就是能执行sql就好,突然一道灵光乍现,既然这样我们用JDBC不就好了,老大说可以你来封装一个。
思路
JDBC的封装要做哪些事?思考如下
- 结果集的封装
- 数据库连接池的封装
开干
结果集的封装
结果集我选用map来封装,map中的key截取sql中的column,同时会将sql中的"_"格式改成驼峰。
结果如下:
数据库连接池的封装
为什么要封装连接池,因为为了稍微可以抗下并发,总不能所有的sql都用一个连接来执行吧,那样速度会慢很多。
看下一个连接的执行效果(100个线程100个并发)
看下20个连接的效果(100个线程100个并发)
看到没这就是连接池的好处,速度提升了20倍有没有!!!
数据库连接池的封装的大意是搞一个集合,将连接放进集合,当有sql要执行时,从集合里去不同的连接来执行sql,注意是不同的连接,准确来说是空闲的连接,如果你总是取集合里的一个连接来执行sql,那么和一个连接有什么区别。
目前我的逻辑是轮流取连接,但是当连接取完后,其他的请求就空转等待其他sql执行完毕。
这里有一个逻辑需要注意尽量采用公平锁的策略,即结束空转的请求尽量去取优先释放的连接,否则执行sql的时候还可能会排队,测试过优化后公平策略的效率同样提升了2-3倍。
代码如下:
@Configuration
public class DBUtil {
@Value("${jdbc.url}")
private String url;
@Value("${username}")
private String user;
@Value("${password}")
private String password;
@Value("${maxConnectNum}")
private Integer maxConnectNum;
@Value("${minConnectNum}")
private Integer minConnectNum;
private volatile List<Connection> pool = new ArrayList<Connection>();
private volatile AtomicInteger index = new AtomicInteger(0);
private Logger logger = LoggerFactory.getLogger(DBUtil.class.getName());
@PostConstruct
public void initConnect(){
for (Integer i = 0; i < minConnectNum; i++) {
pool.add(connect());
}
logger.info("初始化完成,poolSize = {}",pool.size());
}
public SqlResult get(String sql) {
return this.query(sql).get(0);
}
public List<SqlResult> query(String sql) {
Statement statement = null;
Connection connection = null;
List<SqlResult> list = new ArrayList<SqlResult>();
try {
connection = getConnection();
statement = connection.createStatement();
ResultSet res = statement.executeQuery(sql);
String[] cols = dealSql(sql);
while (res.next()) {
SqlResult result = new SqlResult();
for (String col : cols) {
result.put(convert(col), res.getObject(col.replace(" ", "")));
}
list.add(result);
}
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
} finally {
if (statement != null) {
try {
statement.close();
index.decrementAndGet();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return list;
}
private String[] dealSql(String sql) {
return sql.split("from")[0].split("select")[1].split(",");
}
private Connection getConnection() {
try{
if (index.get() == minConnectNum){
if (pool.size() == minConnectNum){
synchronized(DBUtil.class){
if (pool.size() == minConnectNum){
logger.info("最小连接数已耗尽,index = {}",index.get());
for (Integer i = 0; i < maxConnectNum - minConnectNum; i++) {
pool.add(connect());
}
logger.info("最大化完成,poolSize = {}",pool.size());
}
}
}
}
Connection connection = null;
if (index.get() >= maxConnectNum){
synchronized (DBUtil.class){
if (index.get() >= maxConnectNum){
logger.info("最大连接数已耗尽,index = {}",index.get());
while (index.get() >= maxConnectNum){
}
logger.info("等待连接空转结束,index = {}",index.get());
}
connection = pool.get(maxConnectNum - index.get());
index.incrementAndGet();
}
}else {
connection = pool.get(index.get() % pool.size());
index.incrementAndGet();
}
return connection;
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
private Connection connect(){
try {
Class.forName("com.mysql.jdbc.Driver");
return DriverManager.getConnection(url, user, password);
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
private String convert(String var1) {
var1 = var1.replace(" ", "");
String[] s = var1.split("_");
StringBuilder res = new StringBuilder();
for (int i = 0; i < s.length; i++) {
if (i == 0) {
res.append(s[i]);
} else {
res.append(s[i].substring(0, 1).toUpperCase()).append(s[i].substring(1, s[i].length()));
}
}
return res.toString();
}
}
总结
写框架与业务代码不同,第一是要封装的足够通用,第二是要兼顾性能。