[原创]Spring JdbcTemplate 使用总结与经验分享
引言
正确使用JdbcTemplate执行数据库操作
1、Bean声明
@Configuration
class DatabaseConfiguration {
@Bean
public DataSource dataSource() {
DataSource dataSource;
...
return dataSource;
} @Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource());
}
}
2、封装Dao类型
/**
* Created by Ant on 2015/1/1.
*/
public abstract class AntSoftDaoBase {
@Resource(name = "jdbcTemplate")
private JdbcTemplate jdbcTemplate; private String tableName; protected AntSoftDaoBase(String tableName) {
this.tableName = tableName;
} protected JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
} public void clearAll() {
getJdbcTemplate().update("DELETE FROM " + tableName);
} public int count() {
return getJdbcTemplate().queryForObject( "SELECT count(*) FROM " + tableName, Integer.class);
}
}
![](https://images0.cnblogs.com/blog/287167/201501/132036001203671.jpg)
/**
* Created by Ant on 2015/1/1.
*/
@Repository
public class AppDao extends AntSoftDaoBase{
private Logger logger = LoggerFactory.getLogger(getClass()); private static final String TABLE_NAME = "app"; private static final String COLUMN_NAMES = "name, user_id, title, description, ctime, status"; public AppDao() {
super(TABLE_NAME);
} public int create(final AppInfo appInfo) {
...
} public List<AppInfo> list(int pageNo, int pageSize) {
...
} public AppInfo get(int appId) {
...
} public void update(AppInfo appInfo) {
...
}
}
该Dao类型提供了对AppInfo数据的增删查改接口,对这些接口的具体实现,后面再进行详细介绍
3、使用Tomcat-jdbc数据库连接池
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
<version>7.0.42</version>
</dependency>
创建连接池DataSource的逻辑封装在如下方法中,DatabaseConfiguration.dataSource方法内部可以直接调用此方法获取具备连接池功能的DataSource
private DataSource getTomcatPoolingDataSource(String databaseUrl, String userName, String password) {
org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl(databaseUrl);
dataSource.setUsername(userName);
dataSource.setPassword(password); dataSource.setInitialSize(5); // 连接池启动时创建的初始化连接数量(默认值为0)
dataSource.setMaxActive(20); // 连接池中可同时连接的最大的连接数
dataSource.setMaxIdle(12); // 连接池中最大的空闲的连接数,超过的空闲连接将被释放,如果设置为负数表示不限
dataSource.setMinIdle(0); // 连接池中最小的空闲的连接数,低于这个数量会被创建新的连接
dataSource.setMaxWait(60000); // 最大等待时间,当没有可用连接时,连接池等待连接释放的最大时间,超过该时间限制会抛出异常,如果设置-1表示无限等待
dataSource.setRemoveAbandonedTimeout(180); // 超过时间限制,回收没有用(废弃)的连接
dataSource.setRemoveAbandoned(true); // 超过removeAbandonedTimeout时间后,是否进 行没用连接(废弃)的回收
dataSource.setTestOnBorrow(true);
dataSource.setTestOnReturn(true);
dataSource.setTestWhileIdle(true);
dataSource.setValidationQuery("SELECT 1");
dataSource.setTimeBetweenEvictionRunsMillis(1000 * 60 * 30); // 检查无效连接的时间间隔 设为30分钟
return dataSource;
}
关于各数值的配置请根据实际情况调整
配置重连逻辑,以在连接失效是进行自动重连。默认情况下mysql数据库将关闭掉超过8小时的连接,开发的第一个java后端项目,加入数据库连接池后的几天早晨,web平台前几次数据库操作总是失败,配置重连逻辑即可解决
使用HSQL进行数据库操作单元测试
1、内嵌数据库HSQLDB
a)配置HSQL DataSource
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.3.0</version>
</dependency>
@Bean
public DataSource antsoftDataSource() {
DataSource dataSource;
if (antsoftDatabaseIsEmbedded) {
dataSource = getEmbeddedHsqlDataSource();
} else {
dataSource =
getTomcatPoolingDataSource(antsoftDatabaseUrl, antsoftDatabaseUsername, antsoftDatabasePassword);
}
return dataSource;
}
其中antsoftDatabaseIsEmbedded等对象字段值的定义如下
@Value("${antsoft.database.isEmbedded}")
private boolean antsoftDatabaseIsEmbedded; @Value("${antsoft.database.url}")
private String antsoftDatabaseUrl; @Value("${antsoft.database.username}")
private String antsoftDatabaseUsername; @Value("${antsoft.database.password}")
private String antsoftDatabasePassword;
通过@Value指定配置项key名称,运行时通过key查找配置值替换相应字段
配置文件为resources/application.properties
antsoft.database.isEmbedded=false
antsoft.database.url=jdbc:mysql://127.0.0.1:3306/antsoft_app
antsoft.database.username=root
antsoft.database.password=ant
单元测试配置文件为resources/application-test.properties
antsoft.database.isEmbedded=true
表示单测使用内嵌数据库
b)HSQL数据库初始化脚本
private DataSource getEmbeddedHsqlDataSource() {
log.debug("create embeddedDatabase HSQL");
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL).addScript("classpath:db/hsql_init.sql").build();
}
通过addScript指定初始化数据库SQL脚本resources/db/hsql_init.sql,内容如下
SET DATABASE SQL SYNTAX MYS TRUE; CREATE TABLE app (
id int GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1) NOT NULL,
name varchar(64) NOT NULL,
user_id varchar(64) NOT NULL,
title varchar(64) NOT NULL,
description varchar(1024) NOT NULL,
ctime datetime NOT NULL,
status int NOT NULL,
PRIMARY KEY (id),
UNIQUE (name)
); CREATE TABLE app_unique_name (
id int GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1) NOT NULL,
unique_name varchar(64) NOT NULL UNIQUE,
PRIMARY KEY (id)
); ...
HSQL语法与MySql语法存在差异,使用是需注意,我在开发过程中注意到的不同点列举如下
- 不支持tinyint等数据类型,int后不允许附带表示数据长度的括号,如不支持int(11)
- 不支持index索引,但支持unique index
- 不支持AUTO_INCREMENT语法
c)验证你的HSQL脚本
可采用如下方式验证hsql语句正确性
在本地maven仓库中找到hsqldb(正确引入过hsqldb),博主本机目录 C:\Users\ant\.m2\repository\org\hsqldb\hsqldb\2.3.2
执行hsqldb-2.3.2.jar (java -jar hsqldb-2.3.2.jar)
默认窗体一个提示框,点击ok。在右侧输入SQL语句,执行工具栏中中Execuete SQL
如下截图,显示SQL执行成功
上图SQL语句如下
CREATE TABLE app_message (
id bigint GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1) NOT NULL,
app_id int NOT NULL,
message varchar(1024) NOT NULL,
ctime datetime NOT NULL,
status int NOT NULL,
PRIMARY KEY (id)
);
2、编写单元测试覆盖Dao数据库操作
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
@IntegrationTest("server.port=0")
@ActiveProfiles("test")
public @interface AntSoftIntegrationTest {
}
定义测试类型,添加如下注解
@AntSoftIntegrationTest
@RunWith(SpringJUnit4ClassRunner.class)
我对自己代码的期望是,尽可能100%的Dao方法都被单元测试覆盖。
以下代码演示对AppService(其接口实现转发调用AppDao相应接口)进行的基本单元测试,其中测试了create、update及get三种操作
@AntSoftIntegrationTest
@RunWith(SpringJUnit4ClassRunner.class)
public class AppServiceTests {
@Autowired
private AppService appService; @Autowired
private TestService testService; @Before
public void clearApp() {
testService.clearApp();
} @Test
public void testApp() {
final String name = "xxx";
final String userId = "Ant";
final String title = "Hello World";
final String description = "Description for Hello World"; final String updatedName = "xxx";
final String updatedUserId = "Ant";
final String updatedTitle = "Hello World";
final String updatedDescription = "Description for Hello World"; int appId;
{
// 创建应用
AppInfo appInfo = new AppInfo();
appInfo.setName(name);
appInfo.setUserId(userId);
appInfo.setTitle(title);
appInfo.setDescription(description);
appId = appService.createApp(appInfo);
} CheckAppInfo(appId, name, userId, title, description, AppStatus.NORMAL); {
// 更新应用
AppInfo appInfo = new AppInfo();
appInfo.setId(appId);
appInfo.setName(updatedName);
appInfo.setUserId(updatedUserId);
appInfo.setTitle(updatedTitle);
appInfo.setDescription(updatedDescription);
appService.updateApp(appInfo);
} CheckAppInfo(appId, updatedName, updatedUserId, updatedTitle, updatedDescription, AppStatus.NORMAL);
} // 获取应用,并验证数据
private void CheckAppInfo(int appId, String name, String userId, String title, String description,
AppStatus appStatus) {
AppInfo appInfo = appService.getApp(appId);
assertEquals(appId, appInfo.getId());
assertEquals(name, appInfo.getName());
assertEquals(userId, appInfo.getUserId());
assertEquals(title, appInfo.getTitle());
assertEquals(description, appInfo.getDescription());
assertEquals(appStatus, appInfo.getStatus());
}
}
开发经验分享
1、失效的事务
/**
* {@link EnableAutoConfiguration Auto-configuration} for
* {@link DataSourceTransactionManager}.
*
* @author Dave Syer
*/
@Configuration
@ConditionalOnClass({ JdbcTemplate.class, PlatformTransactionManager.class })
public class DataSourceTransactionManagerAutoConfiguration implements Ordered { @Override
public int getOrder() {
return Integer.MAX_VALUE;
} @Autowired(required = false)
private DataSource dataSource; @Bean
@ConditionalOnMissingBean(name = "transactionManager")
@ConditionalOnBean(DataSource.class)
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(this.dataSource);
} @ConditionalOnMissingBean(AbstractTransactionManagementConfiguration.class)
@Configuration
@EnableTransactionManagement
protected static class TransactionManagementConfiguration { } }
由此可见,若未将DataSource声明为Bean,将不会创建transactionManager,@Transactional注解将毫无作用
@Bean
public PlatformTransactionManager transactionManager() {return new DataSourceTransactionManager(myDataSource());
}
默认会使用方法名作为bean的命名,因此此处覆盖了默认的transactionManager Bean对象
2、获取新增数据的自增id
@Repository
public class MessageDao extends AntSoftDaoBase {
private static final String TABLE_NAME = "app_message"; private static final String COLUMN_NAMES = "app_id, message, ctime, status"; protected MessageDao() {
super(TABLE_NAME);
} private static final String SQL_INSERT_DATA =
"INSERT INTO " + TABLE_NAME + " (" + COLUMN_NAMES + ") "
+ "VALUES (?, ?, ?, ?)"; public int create(final MessageInfo messageInfo) {
KeyHolder keyHolder = new GeneratedKeyHolder();
getJdbcTemplate().update(new PreparedStatementCreator() {
public PreparedStatement createPreparedStatement(Connection connection) throws
SQLException {
PreparedStatement ps =
connection.prepareStatement(SQL_INSERT_DATA, Statement.RETURN_GENERATED_KEYS);
int i = 0;
ps.setInt(++i, messageInfo.getAppId());
ps.setString(++i, messageInfo.getMessage());
ps.setTimestamp(++i, new Timestamp(new Date().getTime()));
ps.setInt(++i, 0); // 状态默认为0
return ps;
}
}, keyHolder
);
return keyHolder.getKey().intValue();
}
...
}
3、SQL IN 语句
private static final String SQL_UPDATE_STATUS =
"UPDATE " + TABLE_NAME + " SET "
+ "status = ? "
+ "WHERE id IN (%s)"; public void updateStatus(List<Integer> ids, Status status) {
if (ids == null || ids.size() == 0) {
throw new IllegalArgumentException("ids is empty");
}
String idsText = StringUtils.join(ids, ", ");
String sql = String.format(SQL_UPDATE_STATUS , idsText);
getJdbcTemplate().update(sql, status.toValue());
}
4、查询数据一般方法,及注意事项
AppDao类型中提供get方法,以根据一个appId获取该APP数据,代码如下
private static final String SQL_SELECT_DATA =
"SELECT id, " + COLUMN_NAMES + " FROM " + TABLE_NAME + " WHERE id = ?"; public AppInfo get(int appId) {
List<AppInfo> appInfoList = getJdbcTemplate().query(SQL_SELECT_DATA, new Object[] {appId}, new AppRowMapper());
return appInfoList.size() > 0 ? appInfoList.get(0) : null;
}
注意点:由于主键id会唯一标识一个数据项,有些人会使用queryForObject获取数据项,若未找到目标数据时,该方法并非返回null,而是抛异常EmptyResultDataAccessException。应使用query方法,并检测返回值数据量
AppRowMapper用于解析每行数据并转成Model类型,其代码如下
private static class AppRowMapper implements RowMapper<AppInfo> {
@Override
public AppInfo mapRow(ResultSet rs, int i) throws SQLException {
AppInfo appInfo = new AppInfo();
appInfo.setId(rs.getInt("id"));
appInfo.setName(rs.getString("name"));
appInfo.setUserId(rs.getString("user_id"));
appInfo.setTitle(rs.getString("title"));
appInfo.setDescription(rs.getString("description"));
appInfo.setCtime(rs.getTimestamp("ctime"));
appInfo.setStatus(AppStatus.fromValue(rs.getInt("status")));
return appInfo;
}
}
[原创]Spring JdbcTemplate 使用总结与经验分享的更多相关文章
- 【原创】经验分享:一个小小emoji尽然牵扯出来这么多东西?
前言 之前也分享过很多工作中踩坑的经验: 一个线上问题的思考:Eureka注册中心集群如何实现客户端请求负载及故障转移? [原创]经验分享:一个Content-Length引发的血案(almost.. ...
- 【原创经验分享】WCF之消息队列
最近都在鼓捣这个WCF,因为看到说WCF比WebService功能要强大许多,另外也看了一些公司的招聘信息,貌似一些中.高级的程序员招聘,都有提及到WCF这一块,所以,自己也关心关心一下,虽然目前工作 ...
- 【原创经验分享】JQuery(Ajax)调用WCF服务
最近在学习这个WCF,由于刚开始学 不久,发现网上的一些WCF教程都比较简单,感觉功能跟WebService没什么特别大的区别,但是看网上的介绍,就说WCF比WebService牛逼多少多少,反正我刚 ...
- 我的秋招经验分享(已拿BAT头条网易滴滴)
微信公众号[程序员江湖] 作者黄小斜,斜杠青年,某985硕士,阿里 Java 研发工程师,于 2018 年秋招拿到 BAT 头条.网易.滴滴等 8 个大厂 offer,目前致力于分享这几年的学习经验. ...
- 2019年京东Java研发岗社招面经(面试经历+真题总结+经验分享)!
本篇先以日历形式回顾秋招之路,方便各位参考某厂的处理进度:然后是总结归纳春秋招Java面试题库:最后做个总结还有展望,开始新的征程~ 面试经历京东面试真题面试经验分享1.面试经历 2018年的冬季特别 ...
- 【建议收藏】缺少 Vue3 和 Spring Boot 的实战项目经验?我这儿有啊!
缺少 Vue3 和 Spring Boot 的实战项目经验?缺少学习项目和练手项目?我这儿有啊! 从 2019 年到 2021 年,空闲时间里陆陆续续做了一些开源项目,推荐给大家啊!记得点赞和收藏噢! ...
- Georgia Tech Online Master of Science in Computer Science 项目经验分享
Georgia Tech Online Master of Science in Computer Science 项目经验分享 Posted on 2014/04/22 项目关键词:工科名校,计算机 ...
- IdentityServer4系列之中文文档及实际项目经验分享
0.前言 原文:http://docs.identityserver.io/en/release/声明: 1.目录一至五章节根据IdentityServer英文文档翻译而来,有些内容会根据自己的理解来 ...
- Splunk大数据分析经验分享
转自:http://www.freebuf.com/articles/database/123006.html Splunk大数据分析经验分享:从入门到夺门而逃 Porsche 2016-12-19 ...
随机推荐
- 【机器学习】Boosting和Bagging的差别
boosting和bagging的差别: bagging中的模型是强模型,偏差低,方差高.目标是降低方差.在bagging中,每个模型的bias和variance近似相同,但是互相相关性不太高,因此一 ...
- python学习网址
http://kuanghy.github.io/categories/#Python
- Unity 2D 入门
原文:Introduction to Unity 2D 作者:Sean Duffy 译者:kmyhy 3/15/17 更新说明: 升级至 Unity 5.5. Unity 是一个非常流行和强大的游戏引 ...
- 谈谈Java基础数据类型
Java的基本数据类型 类型 意义 取值 boolean 布尔值 true或false byte 8位有符号整型 -128~127 short 16位有符号整型 -pow(2,15)~pow(2,15 ...
- setInterval()和setTimeout()可以接收更多的参数
setInterval()和setTimeout()可以接收更多的参数,那么这些参数是干什么用的呢?从第三个参数开始,依次用来表示第一个参数(回调函数)传入的参数,例如: setTimeout(fun ...
- requestAnimationFrame 与 cancelAnimationFrame
API接口 Window对象定义了以下两个接口: partial interface Window { long requestAnimationFrame(FrameRequestCallback ...
- golang:bson.ObjectId与string转化
网上资料不好搜,记下来:两个变量:id bson.ObjectIdstr string bson.ObjectId—>string id.Hex() string—>bson.Object ...
- HDU - 6242:Geometry Problem(随机+几何)
Alice is interesting in computation geometry problem recently. She found a interesting problem and s ...
- 阿里云部署django实现公网访问
本博的主要目的是对阿里云部署django实现公网访问进行一次简单的记录,方便日后查询. 内容目录: (1)申请阿里云服务器及安全组配置 (2)实现ssh远程控制 (3)实现ftp文件传输 (4)安装p ...
- PS基础教程[2]渐变工具的使用
PS中的渐变是一个很实用的工具,很多时候都会用到,我们在网页上看到的各种各样的颜色几乎都是一种渐变色,很少有一种颜色一层不变的.那么渐变如何使用呢?本次我们来介绍一下渐变的基本使用方法. 使用方法 1 ...