提示:sqlite最好单线程操作(多线程写会冲突),单这个工具我一般用于小的程序用。所以是一个连接在重复使用没有进行保活之类操作。--新增了单例的读写连接持有,通过队列解决多线程sql执行问题。新增了读线程池(保存每个线程)--
maven:
<properties>
<itextpdf.version>5.5.6</itextpdf.version>
<sqlite.jdbc.version>3.27.2.1</sqlite.jdbc.version>
</properties>
<dependencies>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>${sqlite.jdbc.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/org.springframework.jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.jdbc</artifactId>
<version>3.1.4.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.14</version>
</dependency>
<!--pdf操作-->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>${itextpdf.version}</version>
</dependency>
<dependency>
<groupId>com.itextpdf.tool</groupId>
<artifactId>xmlworker</artifactId>
<version>${itextpdf.version}</version>
</dependency>
<!--中文支持-->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.16</version>
</dependency>
<!--druid log支持-->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<!-- slf4j核心包,由于上面的桥接包里自动依赖进来了,因此不用加了
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>-->
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-simple -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.0-alpha5</version>
<scope>compile</scope>
</dependency>
<!-- apache log 4j start -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.17.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.17.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.0</version>
</dependency>
<!-- end -->
<!-- durid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version> 1.1.23</version>
</dependency>
代码:
sql替换参数还有一种方法:
String sqlTemplate = "INSERT INTO traPdfInfo (isComplete,id, oldPath, dataType,businessDate,noOrno,suffCode,boxNo,caseNo,pieceNo,fileSize,fileMD5,pagesNum,businessDeptCode,businessDept,fileName) VALUES ('${isComplete}','${id}','${oldPath}','${dataType}','${businessDate}','${noOrno}','${suffCode}','${boxNo}','${caseNo}','${pieceNo}','${fileSize}','${fileMD5}','${pagesNum}','${businessDeptCode}','${businessDept}','${fileName}')";
HashMap<String,String> hashMapF = new HashMap((int) (14/0.76));
hashMapF.put("dataType",split[0]);
hashMapF.put("businessDate",split[1]);
.......
StringSubstitutor sub = new StringSubstitutor(hashMapF);
String resolvedString = sub.replace(sqlTemplate);
package net.kong.yumo.utils;
import cn.hutool.core.text.StrBuilder;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.jdbc.datasource.init.ScriptUtils;
import org.sqlite.SQLiteConfig;
import org.sqlite.SQLiteOpenMode;
import java.sql.*;
import java.util.*;
import java.util.concurrent.LinkedBlockingDeque;
/**
* 不建议多线程修改。
* sqlite同一时间只能进行一个写操作,当同时有两个写操作的时候,后执行的只能先等待,如果等待时间超过5秒,
* 就会产生database is locked这种错误.同样一个文件正在写入,重复打开数据库操作更容易导致这种问题的发生。
* 所以单例持有连接,
*/
public class SqlliteSimConnection {
/**
* 读写连接
*/
static Connection c=null;
Statement stmt=null;
static String url="";
/**
*模拟线程池,存放读链接
*/
static Map<String, Connection> threads=new HashMap<>();
/**
* 初始大小 0x7fffffff 2147483647
*/
static LinkedBlockingDeque<String> sqls = new LinkedBlockingDeque<>();
public Connection getThreads() throws SQLException{
if(threads.get(Thread.currentThread().getName())==null ){
Connection readCon = getReadCon(this.url);
setThreads(readCon);
return readCon;
} else{
return threads.get(Thread.currentThread().getName());
}
}
public void setThreads( Connection thread) {
SqlliteSimConnection.threads.put(Thread.currentThread().getName(),thread);
}
public SqlliteSimConnection(String url) {
this.url = url;
}
/**
它在对象被垃圾收集器析构(回收)之前调用
*/
protected void finalize()
{
// 在这里终结代码
close();
}
/**
*
* @param url java不识别大写的E:盘
* @return
*/
private Connection getReadCon(String url) throws SQLException{
try {
Class.forName("org.sqlite.JDBC");
}catch (Exception e) {
e.printStackTrace();
}
SQLiteConfig config = new SQLiteConfig();
config.setOpenMode(SQLiteOpenMode.READONLY);//指定数据库连接 读 。
config.setOpenMode(SQLiteOpenMode.CREATE);//如果数据库不存在,则创建。
/**
* 设置数据库连接运行在多线程模式(没有指定单线程模式的情况下)
* 禁用数据库连接和prepared statement(准备好的语句)上的锁
* 因此不能在多个线程中并发使用同一个数据库连接
*/
config.setOpenMode(SQLiteOpenMode.NOMUTEX);//
// config.setDefaultCacheSize(100000);//页大小默认是 1024Byte,缓存大小默认是 1000 页
/**
* 串行模式支持多线程操作,但是必须*统一使用一个全局的数据库连接*.这一点非常重要。串行模式会打开sqlite3所有的锁,在同一时刻保证只有一个线程能访问。这里可以理解为只有一条指向数据库的连接,多个线程的请求将会在该连接上串行传输。
*/
// config.setOpenMode(SQLiteOpenMode.FULLMUTEX);
// c = DriverManager.getConnection(url);
Connection connection = DriverManager.getConnection(url, config.toProperties());
System.out.println("Opened database successfully");
return connection;
}
public Connection getC() {
if(c==null) {
getC1(this.url);
}
return c;
}
/**
*
* @param url java不识别大写的E:盘
* @return
*/
private synchronized Connection getC1(String url) {
if(c==null) {
try {
Class.forName("org.sqlite.JDBC");
SQLiteConfig config = new SQLiteConfig();
config.setOpenMode(SQLiteOpenMode.READWRITE);//指定数据库连接可以读写。
config.setOpenMode(SQLiteOpenMode.CREATE);//如果数据库不存在,则创建。
/**
* 设置数据库连接运行在多线程模式(没有指定单线程模式的情况下)
* 禁用数据库连接和prepared statement(准备好的语句)上的锁
* 因此不能在多个线程中并发使用同一个数据库连接
*/
config.setOpenMode(SQLiteOpenMode.NOMUTEX);
/**
*页大小默认是 1024Byte(1K),缓存大小默认是 1000 页
*/
config.setDefaultCacheSize(5000);
/**
* 在max ios支持,
* 可以严格保证写入顺序跟提交顺序一致
* 设备开发商为了测评数据好看,往往会对提交的数据进行重排,再统一写入,
* 亦即写入顺序跟App提交的顺序不一致。在某些情况下,例如断电,就可能导致写入文件不一致的情况,导致文件损坏。
*/
config.enableFullSync(true);
/**
* 串行模式支持多线程操作,但是必须*统一使用一个全局的数据库连接*.
* 这一点非常重要。串行模式会打开sqlite3所有的锁,在同一时刻保证只有一个线程能访问。
* 这里可以理解为只有一条指向数据库的连接,多个线程的请求将会在该连接上串行传输。
*/
// config.setOpenMode(SQLiteOpenMode.FULLMUTEX);
/**
* 当synchronous设置为FULL (2), SQLite 数据库引擎在紧急时刻会暂停以确定数据已经写入磁盘。这使 系统崩溃或电源出问题时能确保数据库在重起后不会损坏。FULL synchronous很安全但很慢。
*
* 当synchronous设置为NORMAL, SQLite数据库引擎在大部分紧急时刻会暂停,但不像FULL模式下那么频繁。 NORMAL模式下有很小的几率(但不是不存在)发生电源故障导致数据库损坏的情况。但实际上,在这种情况 下很可能你的硬盘已经不能使用,或者发生了其他的不可恢复的硬件错误。
*
* 设置为synchronous OFF (0)时,SQLite在传递数据给系统以后直接继续而不暂停。若运行SQLite的应用程序崩溃, 数据不会损伤,但在系统崩溃或写入数据时意外断电的情况下数据库可能会损坏。另一方面,在synchronous OFF时 一些操作可能会快50倍甚至更多。在SQLite 2中,缺省值为NORMAL.而在3中修改为FULL。
*/
String property = System.getProperty("db.sqlite.UnSynchronous");
if ("true".equals(property)) {
System.out.println("Synchronous=OFF");
System.out.println("SQLite在传递数据给系统以后直接继续而不暂停。若运行SQLite的应用程序崩溃, 数据不会损伤,但在系统崩溃或写入数据时意外断电的情况下数据库可能会损坏。另一方面,在synchronous OFF时 一些操作可能会快50倍甚至更多。");
config.setSynchronous(SQLiteConfig.SynchronousMode.OFF);
} else {
config.setSynchronous(SQLiteConfig.SynchronousMode.FULL);
}
DriverManager.getConnection(url,config.toProperties());
stmt = c.createStatement();
System.out.println("Opened database successfully");
} catch (Exception e) {
System.out.println("Opened database faile");
e.printStackTrace();
}
}
return c;
}
/**
* 连接到一个现有的数据库。如果数据库不存在, 那么它就会被创建,最后将返回一个数据库对象。
*/
public void Connection() {
c = getC();
}
public void close() {
try {
if (!sqls.isEmpty()) {
exe();
}
stmt.close();
c.close();
threads.clear();
sqls.clear();
// System.out.println("close database successfully");
} catch (SQLException e) {
e.printStackTrace();
}
}
public void addSql(String sql) throws SQLException {
if (sql.endsWith(";")) {
sqls.addLast(sql);
}else {
sqls.addLast(sql+";");
}
}
public void addSqls(List<String> _sqls) throws SQLException {
for (String sql : _sqls) {
if (sql.endsWith(";")) {
sqls.addLast(sql);
}else {
sqls.addLast(sql+";");
}
}
}
/**
* 使用时注意 多线程写
* @return
* @throws SQLException
*/
public void insert(String sql) throws SQLException {
stmt.executeUpdate(sql);
}
/**
* sql替换参数用 :: 包裹
* 字符串类型自动加单引号 使用时注意 多线程写
* @param sql selcet id from t1 where id=:id: and cd=:cd:
* @param par {"id": "dsgds" ,"cd": 1567454}
* @throws SQLException
*/
public void insert(String sql,Map<String,Object> par) throws SQLException {
String[] split = sql.split(":");
StringBuilder newString= new StringBuilder();
for (int i = 0; i < split.length; i++) {
if (i%2==0) {
newString.append(split[i]);
}else {
if (par.get(split[i]) instanceof String) {
newString.append("'");
newString.append(String.valueOf(par.get(split[i])));
newString.append("'");
}else {
if (par.get(split[i]) != null) {
newString.append(String.valueOf(par.get(split[i])));
}else {
throw new IllegalArgumentException("参数缺失:"+split[i]);
}
}
}
}
c.setAutoCommit(false);
stmt.executeUpdate(newString.toString());
c.commit();
}
/**
* 使用时注意 多线程写
* @return
* @throws SQLException
*/
public void insertBatch(String sql,List<Map<String,Object>> par) throws SQLException {
String[] split = sql.split(":");
PreparedStatement preparedStatement = c.prepareStatement(sql);
StrBuilder builder = StrBuilder.create();
for (Map<String, Object> stringObjectMap : par) {
for (int i = 0; i < split.length; i++) {
if (i%2==0) {
builder.append(split[i]);
}else {
if (stringObjectMap.get(split[i]) instanceof String) {
builder.append("'");
builder.append(String.valueOf(stringObjectMap.get(split[i])));
builder.append("'");
}else {
if (stringObjectMap.get(split[i]) != null) {
builder.append(String.valueOf(stringObjectMap.get(split[i])));
}else {
throw new IllegalArgumentException("参数缺失:"+split[i]);
}
}
}
}
preparedStatement.addBatch(builder.toString());
builder.reset();
}
c.setAutoCommit(false);
int [] counts = preparedStatement.executeBatch();
c.commit();
}
public int[] exe() throws SQLException {
int[] ints=null;
try {
Statement statement = c.createStatement();
c.setAutoCommit(false);
int size = this.sqls.size();
for (int i = size; i > 0; i--) {
statement.addBatch(this.sqls.poll());
}
ints = statement.executeBatch();
c.commit();
} catch (SQLException throwables) {
throwables.printStackTrace();
c.rollback();
}
return ints;
}
/**
* 使用时注意 多线程写
* @return
* @throws SQLException
*/
public int[] insert(List<String> sqls) throws SQLException {
int[] ints=null;
try {
c.setAutoCommit(false);
for (String sql:sqls) {
stmt.addBatch(sql);
}
ints = stmt.executeBatch();
c.commit();
} catch (SQLException throwables) {
throwables.printStackTrace();
c.rollback();
}
return ints;
}
/**
* 使用时注意 多线程写
* @return
* @throws SQLException
*
* @param sqls sql数组
* @param batchNum 每多少条提交一次事务 建议大于1万
* @throws SQLException
*/
public void batchInsert(List<String> sqls,int batchNum) throws SQLException {
for (int i = 0; i < sqls.size();) {
c.setAutoCommit(false);
for (; (i % batchNum) == 0 && i < sqls.size() ; i++) {
stmt.addBatch(sqls.get(i));
}
stmt.executeBatch();
c.commit();
}
}
public ResultSet select(String sql) throws SQLException {
return stmt.executeQuery(sql);
}
public List<Map<String, Object>> selectList(String sql) throws SQLException {
return convertList(stmt.executeQuery(sql));
}
public Map<String, Object> selectMap(String sql) throws SQLException {
return convertMap(stmt.executeQuery(sql));
}
public int update( String sql) throws SQLException {
return stmt.executeUpdate(sql);
}
/**
* 执行sql脚本文件 使用Spring工具类
*/
public void runSqlBySpringUtils(String sqlPath) throws Exception {
try {
// FileSystemResource rc = new FileSystemResource(sqlPath);
ClassPathResource rc = new ClassPathResource(sqlPath );
EncodedResource er = new EncodedResource( rc, "utf-8");
ScriptUtils.executeSqlScript(c, rc);
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
public static List<Map<String, Object>> convertList(ResultSet rs) {
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
try {
ResultSetMetaData md = rs.getMetaData();
int columnCount = md.getColumnCount();
while (rs.next()) {
Map<String, Object> rowData = new HashMap<String, Object>();
for (int i = 1; i <= columnCount; i++) {
rowData.put(md.getColumnName(i), rs.getObject(i));
}
list.add(rowData);
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
if (rs != null)
rs.close();
rs = null;
} catch (SQLException e) {
e.printStackTrace();
}
}
return list;
}
public static Map<String, Object> convertMap(ResultSet rs){
Map<String, Object> map = new TreeMap<String, Object>();
try{
ResultSetMetaData md = rs.getMetaData();
int columnCount = md.getColumnCount();
while (rs.next()) {
for (int i = 1; i <= columnCount; i++) {
map.put(md.getColumnName(i), rs.getObject(i));
}
}
} catch (SQLException e){
e.printStackTrace();
} finally {
try {
if (rs != null)
rs.close();
rs = null;
} catch (SQLException e) {
e.printStackTrace();
}
return map;
}
}
}