Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
gu.sql2java.manager.Manager Maven / Gradle / Ivy
package gu.sql2java.manager;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.*;
import static gu.sql2java.SimpleLog.*;
import static gu.sql2java.utils.JDBCUtility.getJavaClass;
import java.math.BigDecimal;
import java.net.URL;
import java.nio.ByteBuffer;
import java.sql.PreparedStatement;
import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicLong;
import javax.sql.DataSource;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.io.ParseException;
import gu.sql2java.BaseBean;
import gu.sql2java.BaseRow;
import gu.sql2java.Constant;
import gu.sql2java.ListenerContainer;
import gu.sql2java.RowMetaData;
import gu.sql2java.SqlRunner;
import gu.sql2java.TableManager;
import gu.sql2java.UnnameRow;
import gu.sql2java.UnnameRowMetaData;
import gu.sql2java.ListenerContainer.FireType;
import gu.sql2java.TableManager.Action;
import gu.sql2java.exception.DaoException;
import gu.sql2java.exception.DataAccessException;
import gu.sql2java.exception.RuntimeDaoException;
import gu.sql2java.geometry.GeometryDataCodec;
import gu.sql2java.geometry.GeometryDataCodecs;
import gu.sql2java.manager.c3p0.C3p0DataSourceFactory;
import gu.sql2java.manager.druid.DruidDataSourceFactory;
import gu.sql2java.manager.parser.ParserSupport;
import gu.sql2java.manager.parser.SqlSyntaxNormalizer;
import gu.sql2java.manager.parser.SqlSyntaxNormalizers;
import gu.sql2java.manager.parser.StatementCache;
import gu.sql2java.manager.sqlite.SqliteDataSourceFactory;
import gu.sql2java.pagehelper.Page;
import gu.sql2java.pagehelper.PageHelper;
import static gu.sql2java.utils.ColumnTransformer.COLUMN_TRANSFORMER;
/**
* The Manager provides connections and manages transactions transparently.
*
* It is a singleton, you get its instance with the getInstance() method.
* All of the XxxxManager classes use the Manager to get database connections.
* Before doing any operation, you must pass either a
* datasource or a jdbc driver/url/username/password.
* @author guyadong
*/
final class Manager implements Constant,SqlRunner
{
static final Map managers = new Hashtable<>();
static final Map aliasManagers = new Hashtable<>();
private static class Singleton{
private static final Manager INSTANCE = new Manager();
}
private final ThreadLocal transactionConnection = new ThreadLocal();
private final ThreadLocal localConnection = new ThreadLocal();
private final ThreadLocal nestedCount = new ThreadLocal();
private volatile DataSource dataSource = null;
private volatile AutoKeyRetrieveType retrieveType;
private volatile ResultSetTypeCast extensiveResultSetTypeCast;
private volatile DataSourceFactory dataSourceFactory;
private volatile String productName;
private volatile DatabaseMetaData databaseMetaData;
private volatile StatementCache statementCache;
private volatile GeometryDataCodec geometryDataCodec;
final DataSourceConfig config;
/**
* Returns the manager singleton instance.
*/
private Manager()
{
this(new DataSourceConfig());
}
Manager(Properties databaseProperties)
{
this(DataSourceConfig.createConfig(databaseProperties));
}
Manager(DataSourceConfig config)
{
this.config = config;
managers.put(checkNotNull(config.jdbcUrl,"jdbcUrl is null"), this);
aliasManagers.put(this.config.alias, this);
}
/**
* Returns the manager singleton instance.
*/
static Manager getInstance()
{
return Singleton.INSTANCE;
}
/** dispose pool */
void disposePool(){
if(null != dataSourceFactory){
dataSourceFactory.destroy(dataSource);
dataSourceFactory = null;
dataSource = null;
}
}
private DataSourceFactory getDataSourceFactory(){
if(null == dataSourceFactory){
synchronized (this) {
if(null == dataSourceFactory){
switch (config.dataSource) {
case DataSourceConfig.DT_SQLITE:
dataSourceFactory = new SqliteDataSourceFactory();
break;
case DataSourceConfig.DT_C3P0:
dataSourceFactory = new C3p0DataSourceFactory();
break;
case DataSourceConfig.DT_DRUID:
dataSourceFactory = new DruidDataSourceFactory();
break;
default:
throw new RuntimeException("UNSUPPORT DATA SOURCE TYPE:" + config.dataSource);
}
}
}
}
return dataSourceFactory;
}
private DatabaseMetaData getDatabaseMetaData(Connection c) throws SQLException{
if(null == databaseMetaData){
synchronized (this) {
if(null == databaseMetaData){
this.databaseMetaData = c.getMetaData();
this.productName = this.databaseMetaData.getDatabaseProductName();
}
}
}
return this.databaseMetaData;
}
DatabaseMetaData getDatabaseMetaData() throws RuntimeDaoException{
if(null == databaseMetaData){
synchronized (this) {
if(null == databaseMetaData){
try {
initDataSourceLazy();
} catch (SQLException e) {
throw new RuntimeDaoException(e);
}
}
}
}
return databaseMetaData;
}
String getProductName() throws RuntimeDaoException{
if(null == productName){
synchronized (this) {
if(null == productName){
getDatabaseMetaData();
}
}
}
return productName;
}
StatementCache getStatementCache() {
// double check
if(null == statementCache){
synchronized (this) {
if(null == statementCache){
SqlFormatter sqlFormatter = SqlFormatters.formatterOf(getProductName());
SqlSyntaxNormalizer normalizer = SqlSyntaxNormalizers.normalizerOf(getProductName());
statementCache = new StatementCache(sqlFormatter, normalizer);
}
}
}
return statementCache;
}
GeometryDataCodec getGeometryDataCodec() {
// double check
if(null == geometryDataCodec) {
synchronized (this) {
if(null == geometryDataCodec) {
geometryDataCodec = GeometryDataCodecs.getGeometryDataCodec(getProductName());
}
}
}
return geometryDataCodec;
}
private void initDataSourceLazy() throws SQLException,RuntimeDaoException{
// double check
if (dataSource == null){
synchronized (this) {
if (dataSource == null){
try {
dataSource = getDataSourceFactory().createDataSource(config);
/* initialize {@link #extensiveResultSetTypeCast} for extensive type cast of database engine,such SQLite */
{
try (Connection c = getConnection0()){
if("SQLite".equals(getDatabaseMetaData(c).getDatabaseProductName())){
extensiveResultSetTypeCast = ResultSetTypeCastSqliteImpl.SQLITE_CAST;
}
}
}
} catch (RuntimeException e) {
Throwables.throwIfInstanceOf(e, RuntimeDaoException.class);
throw new RuntimeDaoException(e);
}
}
}
}
}
void setDataSource(DataSource dataSource){
this.dataSource = dataSource;
}
void setDataSourceFactory(DataSourceFactory dataSourceFactory){
this.dataSourceFactory = dataSourceFactory;
setDataSource(null);
}
private Connection getConnection0() throws SQLException{
return dataSource.getConnection();
}
/**
* Gets an auto commit connection.
*
* Normally you do not need this method that much ;-)
*
* @return an auto commit connection
*/
Connection getConnection() throws SQLException
{
Connection tc = transactionConnection.get();
if (tc != null) {
return tc;
}
Connection c = localConnection.get();
if(null != c) {
nestedCount.set(nestedCount.get() + 1);
return c;
}
initDataSourceLazy();
c = getConnection0();
localConnection.set(c);
nestedCount.set(0);
return c;
}
/**
* Releases the database connection.
*
* Normally you should not need this method ;-)
*/
void releaseConnection(Connection c)
{
Connection tc = transactionConnection.get();
if (tc != null){
return;
}
if(c == localConnection.get()) {
if(null != nestedCount.get() && nestedCount.get() > 0) {
nestedCount.set(nestedCount.get() - 1);
return ;
}else {
nestedCount.remove();
localConnection.remove();
}
}
try{
if (c != null){
c.close();
}
}catch (SQLException e){
throw new RuntimeDaoException(e);
}
}
/**
* Initiates a database transaction.
*
* When working within a transaction, you should invoke this method first.
* The connection is returned just in case you need to set the isolation level.
*
* @return a non-auto commit connection with the default transaction isolation level
*/
private Connection beginTransaction() throws SQLException
{
Connection c = this.getConnection();
c.setAutoCommit(false);
transactionConnection.set(c);
return c;
}
/**
* Releases connection used for the transaction and performs a commit or rollback.
*
* @param commit tells whether this connection should be committed
* true for commit(), false for rollback()
*/
private void endTransaction(boolean commit) throws SQLException
{
Connection c = transactionConnection.get();
if (c == null)
{
return;
}
try
{
if (commit)
{
c.commit();
}
else
{
c.rollback();
}
}
finally
{
c.setAutoCommit(true);
transactionConnection.remove();
releaseConnection(c);
}
}
/**
* Run {@code Callable} as a transaction.
* all exceptions but {@code SQLException} threw by {@code Callable} is wrapped into {@code RuntimeException}
* throw {@code NullPointerException} if {@code fun} be {@code null}
* @param type of return result
* @param fun
* @param transactionListener listener for transaction status
* @return
* @throws RuntimeDaoException
*/
T runAsTransaction(Callable fun,TransactionListener transactionListener) throws RuntimeDaoException{
checkArgument(fun != null, "fun is null");
checkArgument(null != transactionListener,"transactionListener is null");
try {
beginTransaction();
transactionListener.onBegin();
boolean commit = false;
try {
T result = fun.call();
commit = true;
transactionListener.onCommit();
return result;
} catch (SQLException e) {
throw e;
} catch (RuntimeException e) {
throw e;
}catch (Exception e) {
throw new RuntimeException(e);
}finally {
endTransaction(commit);
transactionListener.onEnd();
}
} catch (SQLException e) {
throw new RuntimeDaoException(e);
}
}
/**
* Run {@code Runnable} as a transaction.no return
* @param fun
* @param transactionListener listener for transaction status
* @throws RuntimeDaoException
* @see #runAsTransaction(Callable,TableListener.TransactionListener)
*/
void runAsTransaction(final Runnable fun,TransactionListener transactionListener) throws RuntimeDaoException{
checkArgument(fun != null, "fun is null");
runAsTransaction(new Callable(){
@Override
public Object call() throws Exception {
fun.run();
return null;
}},transactionListener);
}
@Override
public T runAsTransaction(Callable fun) throws RuntimeDaoException{
return runAsTransaction(fun, ListenerContainerLocal.TRANSACTION_LISTENER);
}
@Override
public void runAsTransaction(Runnable fun) throws RuntimeDaoException{
runAsTransaction(fun,ListenerContainerLocal.TRANSACTION_LISTENER);
}
@Override
public T runWithNoPage(Callable fun) throws RuntimeDaoException{
checkArgument(fun != null, "fun is null");
@SuppressWarnings("rawtypes")
Page page = PageHelper.getLocalPage();
boolean enable = false;
if(null != page && (enable = page.isEnable())){
PageHelper.setEnable(false);
}
try {
return fun.call();
} catch (Exception e) {
Throwables.throwIfInstanceOf(e, RuntimeDaoException.class);
Throwables.throwIfUnchecked(e);
throw new RuntimeDaoException(e);
}finally {
if(null != page && enable){
PageHelper.setEnable(enable);
}
}
}
@Override
public void runWithNoPage(Runnable fun) throws RuntimeDaoException{
if(null != fun){
runWithNoPage(new Callable() {
@Override
public Boolean call() throws Exception {
fun.run();
return false;
}
});
}
}
/**
* Closes the passed Statement.
*/
void close(Statement s)
{
try
{
if (s != null) {
s.close();
}
}
catch (SQLException x)
{
log("Could not close statement!: " + x.toString());
}
}
/**
* Closes the passed ResultSet.
*/
void close(ResultSet rs)
{
try
{
if (rs != null) {
rs.close();
}
}
catch (SQLException x)
{
log("Could not close result set!: " + x.toString());
}
}
/**
* Closes the passed Statement and ResultSet.
*/
void close(Statement s, ResultSet rs)
{
close(rs);
close(s);
}
////////////////////////////////////////////////////
// Helper methods for fetching numbers using IDs or names
////////////////////////////////////////////////////
/**
* return all bytes in buffer (position~limit),no change status of buffer
* @param buffer
* @return
*/
private static final byte[] getBytesInBuffer(ByteBuffer buffer){
if(null == buffer){
return null;
}
int pos = buffer.position();
try{
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
return bytes;
}finally{
buffer.position(pos);
}
}
/**
* Set a byte array to the passed prepared statement as a ByteBuffer or as null.
*/
private static void setBytes(int sqlType,PreparedStatement ps, int pos, ByteBuffer bytes) throws SQLException
{
setBytes(sqlType,ps,pos,getBytesInBuffer(bytes));
}
/**
* Set a byte array to the passed prepared statement as a ByteBuffer or as null.
*/
private static void setBytes(int sqlType,PreparedStatement ps, int pos, byte[] bytes) throws SQLException
{
if (null == bytes){
ps.setNull(pos, sqlType);
}else{
ps.setBytes(pos, bytes);
}
}
/**
* Retrieves a date value from the passed result set as a Calendar object.
*/
static Calendar getCalendar(ResultSet rs, int pos) throws SQLException
{
Timestamp date = rs.getTimestamp(pos);
if (rs.wasNull()) {
return null;
}
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
return calendar;
}
/**
* Retrieves a date value from the passed result set as a Calendar object.
*/
static Calendar getCalendar(ResultSet rs, String column) throws SQLException
{
Timestamp date = rs.getTimestamp(column);
if (null == date) {
return null;
}
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
return calendar;
}
/**
* Set a Calendar object to the passed prepared statement as a date or as null.
*/
static void setCalendar(PreparedStatement ps, int pos, Calendar calendar) throws SQLException
{
if (calendar == null)
{
ps.setNull(pos, Types.TIMESTAMP);
}
else
{
ps.setTimestamp(pos, new java.sql.Timestamp(calendar.getTimeInMillis()));
}
}
/**
*
* For compatibility with old version {@link ResultSet} without getObject(int,Class) method
* @param rs
* @param columnIndex
* @param type
* @return
* @throws SQLException
*/
@SuppressWarnings("unchecked")
T getObject(ResultSet rs,int columnIndex, Class type) throws SQLException {
if (type == null) {
throw new SQLException("Type parameter can not be null");
}
Object t = null;
if (type.equals(String.class)) {
t = rs.getString(columnIndex);
} else if (type.equals(BigDecimal.class)) {
t = rs.getBigDecimal(columnIndex);
} else if (type.equals(Boolean.class) || type.equals(Boolean.TYPE)) {
t = Boolean.valueOf(rs.getBoolean(columnIndex));
} else if (type.equals(Byte.class) || type.equals(Byte.TYPE)) {
t = Byte.valueOf(rs.getByte(columnIndex));
} else if (type.equals(Short.class) || type.equals(Short.TYPE)) {
t = Short.valueOf(rs.getShort(columnIndex));
} else if (type.equals(Integer.class) || type.equals(Integer.TYPE)) {
t = Integer.valueOf(rs.getInt(columnIndex));
} else if (type.equals(Long.class) || type.equals(Long.TYPE)) {
t = Long.valueOf(rs.getLong(columnIndex));
} else if (type.equals(Float.class) || type.equals(Float.TYPE)) {
t = Float.valueOf(rs.getFloat(columnIndex));
} else if (type.equals(Double.class) || type.equals(Double.TYPE)) {
t = Double.valueOf(rs.getDouble(columnIndex));
} else if (type.equals(byte[].class)) {
t = rs.getBytes(columnIndex);
} else if (type.equals(ByteBuffer.class)) {
byte[] array = rs.getBytes(columnIndex);
t = (array == null ? null : ByteBuffer.wrap(array));
} else if (type.equals(java.sql.Date.class)) {
t = rs.getDate(columnIndex);
} else if (type.equals(Time.class)) {
t = rs.getTime(columnIndex);
} else if (type.equals(Timestamp.class)) {
t = rs.getTimestamp(columnIndex);
} else if (type.equals(Calendar.class)) {
java.util.Date date = (java.util.Date) rs.getObject(columnIndex);
if (date == null) {
return null;
}
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
t = calendar;
} else if (type.equals(Clob.class)) {
t = rs.getClob(columnIndex);
} else if (type.equals(Blob.class)) {
t = rs.getBlob(columnIndex);
} else if (type.equals(Array.class)) {
t = rs.getArray(columnIndex);
} else if (type.equals(Ref.class)) {
t = rs.getRef(columnIndex);
} else if (type.equals(URL.class)) {
t = rs.getURL(columnIndex);
} else if (Geometry.class.isAssignableFrom(type)) {
// JTS Geomerty
t = getGeometryDataCodec().readGeometryData(rs,columnIndex);
} else {
Object obj = rs.getObject(columnIndex);
try {
t = type.cast(obj);
} catch (ClassCastException e) {
if(null != extensiveResultSetTypeCast){
try{
t = extensiveResultSetTypeCast.cast(obj, type);
} catch (ClassCastException e2) {
throw new SQLException("Conversion not supported for type " + type.getName(), e2);
}
}else{
throw new SQLException("Conversion not supported for type " + type.getName(), e);
}
}
}
return rs.wasNull() ? null : (T)t;
}
static String buildProcedureCall(String packageName, String procedureName, int paramCount) {
return buildProcedureCall(packageName + "." + procedureName, paramCount);
}
static String buildProcedureCall(String procedureName, int paramCount) {
StringBuilder sb = new StringBuilder("{call ").append(procedureName).append("(");
for (int n = 1; n <= paramCount; n++) {
sb.append("?,");
}
if (paramCount > 0) {
sb.setLength(sb.length()-1);
}
return sb.append(")}").toString();
}
/**
* @param targetTypes
* @param ps
* @param action
* @return the count dealt by action
* @since 3.18.3
*/
int runPreparedStatementAsList(Map> targetTypes, PreparedStatement ps, Action action){
checkArgument(null !=ps, "ps is null");
checkArgument(null !=action, "action is null");
ResultSet rs = null;
try {
ps.setFetchSize(100);
rs = ps.executeQuery();
UnnameRowMetaData metaData = new UnnameRowMetaData(rs.getMetaData(), targetTypes);
int count = 0;
while(rs.next()){
BaseBean row = new UnnameRow(metaData);
for(int i=0; i runPreparedStatementAsList(Map> targetTypes,PreparedStatement ps){
ListAction action = new ListAction<>();
runPreparedStatementAsList(targetTypes,ps,action);
return action.getList();
}
@Override
public List runSqlAsList(Map> targetTypes, String sql, Object... argList){
checkArgument(!Strings.isNullOrEmpty(sql),"sql is null or empty");
PreparedStatement ps = null;
Connection c = null;
try {
c = this.getConnection();
AtomicLong count = new AtomicLong(-1L);
/** 先对SQL执行注入攻击检查及归一化处理 */
sql = getStatementCache().normalize(sql, true);
String wrapped = rebuildSelectSql(c,sql, argList, 1, -1, count, DataSourceConfig.isDebugOutput());
// 执行count语句返回空表,就不必再继续执行SQL查询
if(0 == count.get()){
return new ListAction<>().getList();
}
sql = firstNonNull(wrapped, sql);
ps = getStatementCache().prepareStatement(c, sql, false, DataSourceConfig.isDebugOutput(),
"runSqlAsList", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
fillPrepareStatement(ps, argList);
return runPreparedStatementAsList(targetTypes,ps);
} catch (SQLException e) {
throw new RuntimeDaoException(new DataAccessException(e));
} finally {
this.close(ps);
this.releaseConnection(c);
}
}
@Override
public List> runSqlForMap(Map> targetTypes, String sql,Object... argList) throws RuntimeDaoException{
List list = runSqlAsList(targetTypes, sql, argList);
return new ArrayList<>(Lists.transform(list, input->null ==input ? null : new HashMap<>(input.asNameValueMap())));
}
private List fetchOnlyOneColumn(List list,final Class targetType){
if(list.isEmpty()){
return Collections.emptyList();
}
BaseBean row = list.get(0);
Object[] values = row.asValueArray();
checkArgument(values.length == 1, "more than 1 column returned");
if(null == targetType){
return new ArrayList<>(Lists.transform(list,input->null ==input ? null : input.getValue(0)));
}else{
return new ArrayList<>(Lists.transform(list,input->{
if (null == input) {
return null;
}
Object c0 = input.getValue(0);
return COLUMN_TRANSFORMER.cast(c0, c0.getClass(), targetType);
}));
}
}
List runPreparedStatementAsList(Class targetType, PreparedStatement ps) throws RuntimeDaoException{
List list = runPreparedStatementAsList((Map>)null, ps);
return fetchOnlyOneColumn(list,targetType);
}
@Override
public List runSqlAsList(String sql, Object... argList) throws RuntimeDaoException{
return runSqlAsList(Collections.>emptyMap(), sql, argList);
}
@Override
public List runSqlAsList(Class targetType, String sql, Object... argList) throws RuntimeDaoException{
List list = runSqlAsList((Map>)null, sql, argList);
return fetchOnlyOneColumn(list,targetType);
}
@Override
public T runSqlForValue(Class targetType,String sql, Object... argList) throws RuntimeDaoException{
List list = runSqlAsList(targetType,sql,argList);
checkArgument(list.size() == 1,"more than 1 row returned for SQL: %s",sql);
return list.get(0);
}
@Override
public boolean runSql(String sql, Object[] argList) {
checkArgument(!Strings.isNullOrEmpty(sql),"sql is null or empty");
if(DataSourceConfig.isDebugOutput()){
log("runSql:" + sql);
}
PreparedStatement ps = null;
Connection c = null;
try {
c = getConnection();
ps = getStatementCache().prepareStatement(c, sql, true, DataSourceConfig.isDebugOutput(), "runSql");
fillPrepareStatement(ps, argList);
return ps.execute();
} catch (SQLException e) {
throw new RuntimeDaoException(e);
} finally {
close(ps);
releaseConnection(c);
}
}
@Override
public int runSql(String sql) {
checkArgument(!Strings.isNullOrEmpty(sql),"sql is null or empty");
if(DataSourceConfig.isDebugOutput()){
log("runSql:" + sql);
}
Statement stat = null;
Connection c = null;
//System.out.printf("sql string:\n" + sql + "\n");
try {
c = getConnection();
stat = c.createStatement();
return stat.executeUpdate(sql);
} catch (SQLException e) {
throw new RuntimeDaoException(e);
} finally {
close(stat);
releaseConnection(c);
}
}
@Override
public long rowCountOf(String sql) throws RuntimeDaoException{
Connection c = null;
try {
c = this.getConnection();
return rowCountOf(c, sql,null, null, DataSourceConfig.isDebugOutput(), true);
} catch (SQLException e) {
throw new RuntimeDaoException(e);
} finally {
releaseConnection(c);
}
}
T runPreparedStatementForValue(Class targetType,PreparedStatement ps) throws RuntimeDaoException{
return runWithNoPage(()->{
List list = runPreparedStatementAsList(targetType,ps);
checkArgument(list.size() == 1,"more than 1 row or none row returned");
return list.get(0);
});
}
/**
* 填充PreparedStatement中的参数对象
*
* @param ps
* @param argList
* @throws SQLException
*/
protected void fillPrepareStatement(PreparedStatement ps, Object[] argList) throws SQLException{
if (!(argList == null || ps == null)) {
for (int i = 0; i < argList.length; i++) {
if (argList[i] instanceof ByteBuffer) {
ps.setBytes(i + 1, getBytesInBuffer((ByteBuffer) argList[i]));
} if (argList[i] instanceof Geometry) {
ps.setBytes(i + 1, getGeometryDataCodec().toWKB(argList[i]));
} else {
ps.setObject(i + 1, argList[i]);
}
}
}
}
/**
* 填充PreparedStatement中的参数对象
* @param ps
* @param pos parameter Index, the first parameter is 1, the second is 2, ...
* @param value parameter value
* @param sqlType the SQL type code defined in java.sql.Types
* @param columnTypeName type name of column
* @throws SQLException
*/
protected void fillPreparedStatement(PreparedStatement ps,int pos,Object value,int sqlType, String columnTypeName)
throws SQLException {
if(value instanceof ByteBuffer){
setBytes(sqlType, ps, pos, (ByteBuffer)value);
} else if(getGeometryDataCodec().isGeometryDataType(columnTypeName)) {
if(sqlType == Types.OTHER) {
setBytes(sqlType, ps, pos, getGeometryDataCodec().toWKB(value));
}else if(String.class.equals(getJavaClass(sqlType))) {
try {
ps.setObject(pos,getGeometryDataCodec().toWKT(value),sqlType);
} catch (ParseException e) {
throw new SQLException(e);
}
}else {
// UNKONW TO DO
ps.setObject(pos,value,sqlType);
}
}else{
ps.setObject(pos,value,sqlType);
}
}
/**
* For those who do not want to read below, please simply pick up the
* configuration associated with your database.
*
* More explanation:
* When you save a bean whose primary key is numeric and has no value set,
* we assume that you want sql2java to retrieve a key's value generated
* on the database side.
*
* generatedkey.retrieve can take 3 values:
*
* auto - the standard approach when you have a JDBC 3.0 driver.
*
* before - the key's value is retrieved before inserting the record.
*
* after - the key's value is retrieved after inserting the record
* @author guyadong
*
*/
enum AutoKeyRetrieveType{
auto,after,before
}
/**
*
* For those who do not want to read below, please simply pick up the
* configuration associated with your database.
*
* More explanation:
* When you save a bean whose primary key is numeric and has no value set,
* we assume that you want sql2java to retrieve a key's value generated
* on the database side.
*
* If you set it to before or after you also need to configure the
* autogeneratedkey.statement properties.
* is replaced at code generation time by the table name.
* is replaced at code generation time by the column name.
* You may adjust this properties to fit your own naming convention.
*
* PICK THE CONFIGURATION ASSOCIATED WITH YOUR DATABASE
* (or create one, but in that case let us know so we can add it here... :-)
*/
private static final ImmutableMap AUTOINC_MECHANISM = ImmutableMap.builder()
/*-- HSQL ------*/
.put("hsql.generatedkey.retrieve","after")
.put("hsql.generatedkey.statement","CALL IDENTITY()")
/*-- ORACLE 8i ------*/
.put("oracle.generatedkey.retrieve","before")
.put("oracle.generatedkey.statement","SELECT SEQ_.nextval FROM DUAL")
/*-- MaxDB (SapDB) ------*/
.put("maxdb.generatedkey.retrieve","before")
.put("maxdb.generatedkey.statement","SELECT SEQ__.nextval FROM DUAL")
/*-- MYSQL (without jdbc 3.0 driver) ------*/
.put("mysql.generatedkey.retrieve","after")
.put("mysql.generatedkey.statement","SELECT last_insert_id()")
.put("mysql.page.query","LIMIT_OFFSET")
/*-- SQL SERVER ------*/
.put("sqlserver.generatedkey.retrieve","after")
.put("sqlserver.generatedkey.statement","SELECT @@IDENTITY")
.put("sqlserver.page.query","OFFSET_FETCH")
/*-- SYBASE ------*/
.put("sybase.generatedkey.retrieve","after")
.put("sybase.generatedkey.statement","SELECT @@IDENTITY")
/*-- POSTGRESQL ------*/
.put("postgresql.generatedkey.retrieve","before")
.put("postgresql.generatedkey.statement","SELECT nextval('__SEQ')")
.put("postgresql.page.query","LIMIT_OFFSET")
/*-- INFORMIX ------*/
.put("informix.generatedkey.retrieve","after")
.put("informix.generatedkey.statement","SELECT dbinfo('sqlca.sqlerrd1') FROM systables WHERE tabid=1")
/*-- FIREBIRD ------
- need to create "sequence" number table first "create generator SEQ_"
- the 1 is how much to increment the sequence*/
.put("firebird.generatedkey.retrieve","before")
.put("firebird.generatedkey.statement","SELECT GEN_ID(SEQ_, 1) FROM RDB$DATABASE")
/*-- DERBY ------*/
.put("derby.generatedkey.retrieve","after")
.put("derby.generatedkey.statement","VALUES IDENTITY_VAL_LOCAL()")
.put("sqlite.page.query","LIMIT_OFFSET")
/*-- PHOENIX(not testing) ------*/
.put("phoenix.generatedkey.retrieve","before")
.put("phoenix.generatedkey.statement","SELECT NEXT VALUE FOR SEQ__")
.put("phoenix.insert.values","false")
.put("phoenix.page.query","LIMIT_OFFSET")
.build();
private static final String getMechanismPropertyWithSuffix(String productName,String suffix) {
StringTokenizer token = new StringTokenizer(productName);
while(token.hasMoreTokens()){
String value = AUTOINC_MECHANISM.get(token.nextToken().toLowerCase() + "." + suffix);
if(value != null){
return value;
}
}
throw new NullPointerException(String.format( "NOT FOUND %s property for %s database",suffix,productName));
}
private synchronized void autokeyMechanismInit() throws SQLException{
if(getDatabaseMetaData().supportsGetGeneratedKeys()){
config.generatedkeyRetrieve = "auto";
config.generatedkeyStatement = null;
}else{
config.generatedkeyRetrieve = getMechanismPropertyWithSuffix(getProductName(),RETRIEVE_SUFFIX).toLowerCase();
config.generatedkeyStatement = getMechanismPropertyWithSuffix(getProductName(),STATEMENT_SUFFIX);
}
}
private void initPageQueryImplType(){
config.pageQueryImplType = PageQueryImplType.NO_IMPL;
try {
config.pageQueryImplType = PageQueryImplType.valueOf(getMechanismPropertyWithSuffix(getProductName(),PAGE_QUERY_SUFFIX).toUpperCase());
} catch (NullPointerException e) {
} catch (IllegalArgumentException e) {
}
}
PageQueryImplType getPageQueryImplType(){
// double check
if(config.pageQueryImplType == null){
synchronized (this) {
if(config.pageQueryImplType == null){
initPageQueryImplType();
}
}
}
return config.pageQueryImplType;
}
String getGeneratedkeyRetrieve() throws SQLException{
// double check
if(config.generatedkeyRetrieve == null){
synchronized (this) {
if(config.generatedkeyRetrieve == null){
autokeyMechanismInit();
}
}
}
return config.generatedkeyRetrieve;
}
boolean isSupportInsertValues() throws SQLException{
// double check
if(config.supportInsertValues == null){
synchronized (this) {
if(config.supportInsertValues == null){
config.supportInsertValues = true;
try {
config.supportInsertValues =
Boolean.valueOf(getMechanismPropertyWithSuffix(getProductName(),INSERT_VALUES_SUFFIX).toLowerCase());
} catch (NullPointerException e) {
} catch (IllegalArgumentException e) {
}
}
}
}
return config.supportInsertValues;
}
AutoKeyRetrieveType getGeneratedkeyRetrieveType() throws SQLException{
if(retrieveType == null){
synchronized (this) {
if(retrieveType == null){
retrieveType = AutoKeyRetrieveType.valueOf(getGeneratedkeyRetrieve());
}
}
}
return retrieveType;
}
String getGeneratedkeyStatement(){
// double check
if(config.generatedkeyStatement == null && !AutoKeyRetrieveType.auto.equals(retrieveType)){
synchronized (this) {
if(config.generatedkeyStatement == null && !AutoKeyRetrieveType.auto.equals(retrieveType)){
try {
autokeyMechanismInit();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
return config.generatedkeyStatement;
}
FireType getFireType(){
return config.fireType;
}
/**
* fill the given prepared statement with the bean values and a search type
* @param metaData
* @param ps the PreparedStatement that will be filled
* @param bean the bean to use for creating the where clauses
* @param searchType exact ? like ? starting like ?
* @param fillNull wether fill null for null field
* @param checkModified check column modified flag if true
* @param fieldList table of the field's associated constants,if null as all field
* @param dirtyCount
* @return the number of clauses returned
*/
int fillPreparedStatement(RowMetaData metaData,PreparedStatement ps, BaseBean bean, int searchType,boolean fillNull, boolean checkModified, int[] fieldList, int dirtyCount) throws DaoException
{
if (bean == null) {
return 0;
}
try
{
fieldList = firstNonNull(fieldList, metaData.defaultColumnIdList);
for(int columnId:fieldList){
Object value = bean.getJdbcValue(columnId);
if(null != value || fillNull){
if(!checkModified || bean.isModified(columnId)){
if(String.class == metaData.columnTypeOf(columnId)){
switch (searchType) {
case SEARCH_EXACT:
fillPreparedStatement(ps, ++dirtyCount , value, metaData.sqlTypes[columnId], metaData.columnTypeNames.get(columnId));
break;
case SEARCH_LIKE:
fillPreparedStatement(ps, ++dirtyCount , SQL_LIKE_WILDCARD + value + SQL_LIKE_WILDCARD, metaData.sqlTypes[columnId], metaData.columnTypeNames.get(columnId));
break;
case SEARCH_STARTING_LIKE:
fillPreparedStatement(ps, ++dirtyCount , SQL_LIKE_WILDCARD + value, metaData.sqlTypes[columnId], metaData.columnTypeNames.get(columnId));
break;
case SEARCH_ENDING_LIKE:
fillPreparedStatement(ps, ++dirtyCount , value + SQL_LIKE_WILDCARD, metaData.sqlTypes[columnId], metaData.columnTypeNames.get(columnId));
break;
default:
throw new DaoException("Unknown search type : " + searchType);
}
}else{
fillPreparedStatement(ps, ++dirtyCount , value, metaData.sqlTypes[columnId], metaData.columnTypeNames.get(columnId));
}
}
}
}
}
catch(SQLException e)
{
throw new DataAccessException(e);
}
return dirtyCount;
}
static class LocalFillPreparedStatementArgs{
BaseRow bean;
int searchType;
boolean fillNull;
public LocalFillPreparedStatementArgs(BaseRow bean, int searchType, boolean fillNull) {
super();
this.bean = bean;
this.searchType = searchType;
this.fillNull = fillNull;
}
}
private static final ThreadLocal localFillPreparedStatementArgs = new ThreadLocal<>();
static void setLocalfillPreparedStatement(BaseRow bean, int searchType,boolean fillNull){
localFillPreparedStatementArgs.set(new LocalFillPreparedStatementArgs(bean,searchType,fillNull));
}
static void removeLocalfillPreparedStatement(){
localFillPreparedStatementArgs.remove();
}
private void localFillPreparedStatement(PreparedStatement ps) throws DaoException{
LocalFillPreparedStatementArgs args = localFillPreparedStatementArgs.get();
if (null != args && null != args.bean) {
fillPreparedStatement(args.bean.fetchMetaData(),ps,args.bean,args.searchType,args.fillNull, true, null, 0);
}
}
/**
* return SQL SELECT statement,if not rebuilt,return {@code null}
*
* @param c
* @param sql SQL SELECT statement
* @param argList the arguments to use fill given prepared statement,may be null
* @param startRow the start row to be used (first row = 1, last row=-1)
* @param numRows the number of rows to be retrieved (all rows = a negative number)
* @param count [out]row count
* @param debug output debug information if {@code true}
* @return rebuilt SQL statement if {@code sql} be rebuilt
* @throws SQLException
*/
String rebuildSelectSql(Connection c, String sql,Object[] argList,Integer startRow, Integer numRows, AtomicLong count, boolean debug) throws SQLException{
if(null != sql){
startRow = firstNonNull(startRow, 1);
numRows = firstNonNull(numRows, -1);
PageQueryImplType pageQueryImplType = getPageQueryImplType();
String wrapped = pageQueryImplType.wrap(sql, startRow, numRows);
@SuppressWarnings("rawtypes")
Page page;
if(null != wrapped && null != (page = PageHelper.getLocalPage()) && page.isEnable() && page.isCount()){
/**
* 计算查询记录的总数
*/
long total = rowCountOf(c,wrapped,argList, page.getCountColumn(), debug, false);
page.setTotal(total);
if(null != count){
count.set(total);
}
}
return wrapped;
}
return sql;
}
/**
* return row count of sql
* @param c
* @param sql
* @param argList the arguments to use fill given prepared statement,may be null
* @param countColumn column name for count() function
* @param debug
* @param injectAnalyze run injection attack analysis if true
* @throws SQLException
*/
long rowCountOf(Connection c, String sql, Object[] argList, String countColumn, boolean debug, boolean injectAnalyze) throws SQLException{
checkArgument(!Strings.isNullOrEmpty(sql),"sql is null or empty");
/**
* 计算查询记录的总数
*/
String countSql = ParserSupport.countSql(sql, countColumn);
PreparedStatement ps = null;
try {
ps = getStatementCache().prepareStatement(c, countSql, injectAnalyze, debug,
"rowCountOf",
ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
fillPrepareStatement(ps, argList);
localFillPreparedStatement(ps);
return runPreparedStatementForValue(Long.class,ps);
} finally {
close(ps);
}
}
}