数据源(datasource包&jdbc包)
在datasource包中ibatis提供三类对外数据源factory,分别为:SimpleDataSourceFactory、JndiDataSourceFactory、DbcpDataSourceFactory。
SimpleDataSourceFactory
SimpleDataSourceFactory对外提供简单数据源,接口定义如下:
- public class SimpleDataSourceFactory implements DataSourceFactory {
- private DataSource dataSource;
- public void initialize(Map map) {// map是从sqlMapConfig.xml配置文件拆分出来的配置信息,用于初始化数据源。
- dataSource = new SimpleDataSource(map);
- }
- }
工厂定义很简单,主要对外提供一个javax.sql.DataSource数据源。
对于数据源来说,配置信息涉及到:
- // Required Properties
- private static final String PROP_JDBC_DRIVER = "JDBC.Driver";
- private static final String PROP_JDBC_URL = "JDBC.ConnectionURL";
- private static final String PROP_JDBC_USERNAME = "JDBC.Username";
- private static final String PROP_JDBC_PASSWORD = "JDBC.Password";
- private static final String PROP_JDBC_DEFAULT_AUTOCOMMIT = "JDBC.DefaultAutoCommit";
- // Optional Properties
- private static final String PROP_POOL_MAX_ACTIVE_CONN = "Pool.MaximumActiveConnections";
- private static final String PROP_POOL_MAX_IDLE_CONN = "Pool.MaximumIdleConnections";
- ...
典型的数据源配置如下:
- <transactionManager type="JDBC">
- <dataSource type="DBCP">
- <property name="JDBC.Driver" value="${driver}"/>
- <property name="JDBC.ConnectionURL" value="${url}"/>
- <property name="JDBC.Username" value="${username}"/>
- <property name="JDBC.Password" value="${password}"/>
- <property name="Pool.MaximumActiveConnections" value="8"/>
- <property name="Pool.MaximumIdleConnections" value="8"/>
- ....
- </dataSource>
- </transactionManager>
知道了需要配置的参数后,第一件是就是调用SimpleDataSource的initialize(Map props)做初始化工作,主要需要校验、设置默认值或赋值等。
大堆的初始化工作中就不说了,注意到initialize方法里有个driverProps变量,用于存储用户在配置中以Driver.开头的配置,如<property name="Driver.xxx" values="xxx" />,在建立连接池的时候,加载这些配置(DriverManager.getConnection(jdbcUrl, driverProps))。
JndiDataSourceFactory
同样ibatis也支持JNDI来初始化datasource,主要用于让服务器容器管理连接池。同样初始化工作、获取数据源实现如下:
- public void initialize(Map properties) {
- try {
- InitialContext initCtx = null;
- Hashtable context = getContextProperties(properties);
- if (context == null) {
- initCtx = new InitialContext();
- } else {
- initCtx = new InitialContext(context);
- }
- if (properties.containsKey("DataSource")) {
- dataSource = (DataSource) initCtx.lookup((String) properties.get("DataSource"));
- } else if (properties.containsKey("DBJndiContext")) { // LEGACY --Backward compatibility
- dataSource = (DataSource) initCtx.lookup((String) properties.get("DBJndiContext"));
- } else if (properties.containsKey("DBFullJndiContext")) { // LEGACY --Backward compatibility
- dataSource = (DataSource) initCtx.lookup((String) properties.get("DBFullJndiContext"));
- } else if (properties.containsKey("DBInitialContext")
- && properties.containsKey("DBLookup")) { // LEGACY --Backward compatibility
- Context ctx = (Context) initCtx.lookup((String) properties.get("DBInitialContext"));
- dataSource = (DataSource) ctx.lookup((String) properties.get("DBLookup"));
- }
- } catch (NamingException e) {
- throw new SqlMapException("There was an error configuring JndiDataSourceTransactionPool. Cause: " + e, e);
- }
- }
DbcpDataSourceFactory
使用了第三方apache的dbcp来管理连接池。工厂接口很简单,如下:
- public class DbcpDataSourceFactory implements DataSourceFactory {
- private DataSource dataSource;
- public void initialize(Map map) {
- DbcpConfiguration dbcp = new DbcpConfiguration(map);
- dataSource = dbcp.getDataSource();
- }
- }
加载dbcp的时候,有个判断配置Map是否含有“JDBC.Driver“属性,如下:
- BasicDataSource basicDataSource = null;
- if (map.containsKey("JDBC.Driver")) {
- basicDataSource = new BasicDataSource();
- String driver = (String) map.get("JDBC.Driver");
- String url = (String) map.get("JDBC.ConnectionURL");
- String username = (String) map.get("JDBC.Username");
- ...
如果有:则正常加载所有属性(源码省略),这里注意IBATIS只为DBCP加载一定量的配置,其他DBCP配置请以Driver.开头。
如果没有JDBC.Driver,则利用反射的知识进行赋值,在赋值的时候ibatis做了点类型转换的工作,因为源数据都是String字符类型,需要反射invoke到方法里,需要做类型变换,如下:
- private BasicDataSource newDbcpConfiguration(Map map) {
- BasicDataSource basicDataSource = new BasicDataSource();
- Iterator props = map.keySet().iterator();
- while (props.hasNext()) {
- String propertyName = (String) props.next();
- if (PROBE.hasWritableProperty(basicDataSource, propertyName))//判断basticDataSource对象有没有propertyName属性 {
- String value = (String) map.get(propertyName);
- Object convertedValue = convertValue(basicDataSource, propertyName, value);// 将value类型转换成basicDataSource对象propertyName变量的类型
- PROBE.setObject(basicDataSource, propertyName, convertedValue);
- }
- }
- return basicDataSource;
- }
- private Object convertValue(Object object, String propertyName, String value) {
- Object convertedValue = value;
- Class targetType = PROBE.getPropertyTypeForSetter(object, propertyName);// 获取object对象propertyName变量的类型
- if (targetType == Integer.class || targetType == int.class) {
- convertedValue = Integer.valueOf(value);
- } else if (targetType == Long.class || targetType == long.class) {
- convertedValue = Long.valueOf(value);
- } else if (targetType == Boolean.class || targetType == boolean.class) {
- convertedValue = Boolean.valueOf(value);
- }
- return convertedValue;
- }
以上关于ibatis三类数据源加载就完成了,对于加载数据源,看到ibatis基本没有什么限制,甚至可以不配置任何数据源信息,这为外部应用加载其他数据源提供了很大灵活性。
连接池(SimpleDataSource)
对于JNDIDataSource和DBCP都有自己的连接池管理,而SimpleDataSource由ibatis自己管理着连接,所有需要有自己的实现。在上面创建SimpleDataSource的时,ibatis并不马上建立自己的连接池的,而是在第一次使用Connection时触发连接池的创建。
看看 public Connection getConnection()方法:
- public Connection getConnection() throws SQLException {
- return popConnection(jdbcUsername, jdbcPassword).getProxyConnection();
- }
ibatis的连接池由2个数组分别存放空闲连接和非空闲连接:
- private final Object POOL_LOCK = new Object();
- private List idleConnections = new ArrayList();
- private List activeConnections = new ArrayList();
ibatis的连接池实现大致解读为:
- SimplePooledConnection conn = null;
- while (conn == null) {
- synchronized (POOL_LOCK) {
- if (idleConnections.size() > 0) {
- // 有空闲连接,从池中取,这里因为用的ArrayList,效率有待提高,remove会触发数组的复制。
- conn = (SimplePooledConnection) idleConnections.remove(0);
- } else {
- // 无空闲连接且活动连接小于最大活动数,则创建新的连接池
- if (activeConnections.size() < poolMaximumActiveConnections) {
- // Can create new connection
- if (useDriverProps) {
- conn = new SimplePooledConnection(DriverManager.getConnection(jdbcUrl, driverProps), this);
- } else {
- conn = new SimplePooledConnection(DriverManager.getConnection(jdbcUrl, jdbcUsername, jdbcPassword), this);
- }
- } else {
- // Cannot create new connection,当前活动连接数大于最大值,不能创建新连接。
- SimplePooledConnection oldestActiveConnection = (SimplePooledConnection) activeConnections.get(0);
- long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
- // 尝试移除oldest活动连接,判断是否超时
- if (longestCheckoutTime > poolMaximumCheckoutTime) {
- // Can claim overdue connection
- ...
- if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
- oldestActiveConnection.getRealConnection().rollback();// 回滚
- }
- conn = new SimplePooledConnection(oldestActiveConnection.getRealConnection(), this);
- oldestActiveConnection.invalidate();// 当前连接已经超时却为活动状态,判为无效。
- } else {
- // Must wait 没有可用的连接池,最坏情况,会造成当先线程等待
- try {
- if (!countedWait) {
- hadToWaitCount++;
- countedWait = true;
- }
- long wt = System.currentTimeMillis();
- POOL_LOCK.wait(poolTimeToWait);
- accumulatedWaitTime += System.currentTimeMillis() - wt;
- } catch (InterruptedException e) {
- break;
- }
- }
- }
- }
- if (conn != null) {
- // 非新建立连接,从空闲队列中取的连接池,需要重置状态
- if (conn.isValid()) {
- if (!conn.getRealConnection().getAutoCommit()) {
- conn.getRealConnection().rollback();
- }
- conn.setConnectionTypeCode(assembleConnectionTypeCode(jdbcUrl, username, password));
- conn.setCheckoutTimestamp(System.currentTimeMillis());
- conn.setLastUsedTimestamp(System.currentTimeMillis());
- activeConnections.add(conn);
- requestCount++;
- accumulatedRequestTime += System.currentTimeMillis() - t;
- } else {
- badConnectionCount++;
- localBadConnectionCount++;
- conn = null;
- if (localBadConnectionCount > (poolMaximumIdleConnections + 3)) {// 当前活动连接超时成为“坏”连接,抛出程序异常。
- throw new SQLException("SimpleDataSource: Could not get a good connection to the database.");
- }
- }
- }
- }
- }
再看看pushConnection连接返回连接池操作,能看出ibatis池的一点异同。
- private void pushConnection(SimplePooledConnection conn)
- throws SQLException {
- synchronized (POOL_LOCK) {
- activeConnections.remove(conn);// 从活动队列移除conn
- if (conn.isValid()) {
- if (idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == getExpectedConnectionTypeCode()) {
- accumulatedCheckoutTime += conn.getCheckoutTime();
- if (!conn.getRealConnection().getAutoCommit()) {
- conn.getRealConnection().rollback();
- }
- SimplePooledConnection newConn = new SimplePooledConnection(conn.getRealConnection(), this);// <span style="color: #ff0000;">精华,把移除的conn中的Connection重新赋值给新的SimplePooledConnection,而原来的SimplePooledConnection对象会销毁。
- </span> idleConnections.add(newConn);
- newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
- newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
- conn.invalidate();
- POOL_LOCK.notifyAll();
- } else {
- accumulatedCheckoutTime += conn.getCheckoutTime();
- if (!conn.getRealConnection().getAutoCommit()) {
- conn.getRealConnection().rollback();
- }
- conn.getRealConnection().close();
- conn.invalidate();
- }
- } else {
- badConnectionCount++;
- }
- }
- }
以上的精华就在:SimplePooledConnection newConn = new SimplePooledConnection(conn.getRealConnection(), this);//精华,把移除的conn中的Connection重新赋值给新的SimplePooledConnection,而原来的SimplePooledConnection对象会销毁。
对于对象池操作,如果要把老对象返回到池中,必定需要做清理工作,而ibatis的连接池在做返回池中并没有保留老对象,而是直接摒弃老对象,new一个新对象且载入老对象的Connection入到idleConnections队列中。
注意这里的remove()操作只是去除了引用,而非内存对象,GC暂时不会回收:)
推荐对于非容器管理连接池的话,用DBCP。
事务(transaction)
ibatis涉及到3类事务,分别为JDBC事务,JTA事务,可扩展事务。
说道事务一般会涉及到事务的接口、状态、配置、分布式事务等。ibatis提供的事务接口看上去很简单:
- public interface Transaction {
- public void commit() throws SQLException, TransactionException;
- public void rollback() throws SQLException, TransactionException;
- public void close() throws SQLException, TransactionException;
- public Connection getConnection() throws SQLException, TransactionException;
- }
事务状态有:STATE_STARTED、STATE_COMMITTED、STATE_ENDED、STATE_USER_PROVIDED
事务配置有:
- public interface TransactionConfig {
- public DataSource getDataSource();
- public void setDataSource(DataSource ds);
- public void initialize(Properties props) throws SQLException, TransactionException;
- public Transaction newTransaction(int transactionIsolation) throws SQLException, TransactionException;
- public int getMaximumConcurrentTransactions();
- public void setMaximumConcurrentTransactions(int maximumConcurrentTransactions);
- }
JDBC事务
- public class JdbcTransaction implements Transaction {
- private static final Log connectionLog = LogFactory.getLog(Connection.class);
- private DataSource dataSource;
- private Connection connection;
- private IsolationLevel isolationLevel = new IsolationLevel();
- public JdbcTransaction(DataSource ds, int isolationLevel) throws TransactionException {
- // Check Parameters
- dataSource = ds;
- if (dataSource == null) {
- throw new TransactionException("JdbcTransaction initialization failed. DataSource was null.");
- }
- this.isolationLevel.setIsolationLevel(isolationLevel);
- }
- private void init() throws SQLException, TransactionException {
- // Open JDBC Transaction
- connection = dataSource.getConnection();
- if (connection == null) {
- throw new TransactionException("JdbcTransaction could not start transaction. Cause: The DataSource returned a null connection.");
- }
- // Isolation Level
- isolationLevel.applyIsolationLevel(connection);
- // AutoCommit
- if (connection.getAutoCommit()) {
- connection.setAutoCommit(false);
- }
- // Debug
- if (connectionLog.isDebugEnabled()) {
- connection = ConnectionLogProxy.newInstance(connection);
- }
- }
- public void commit() throws SQLException, TransactionException {
- if (connection != null) {
- connection.commit();
- }
- }
- public void rollback() throws SQLException, TransactionException {
- if (connection != null) {
- connection.rollback();
- }
- }
- public void close() throws SQLException, TransactionException {
- if (connection != null) {
- try {
- isolationLevel.restoreIsolationLevel(connection);
- } finally {
- connection.close();
- connection = null;
- }
- }
- }
- public Connection getConnection() throws SQLException, TransactionException {
- if (connection == null) {
- init();
- }
- return connection;
- }
- }
JTA事务(JTA事务一般由第三方实现,ibatis不关心实现)
- public class JtaTransaction implements Transaction {
- private static final Log connectionLog = LogFactory.getLog(Connection.class);
- private UserTransaction userTransaction;
- private DataSource dataSource;
- private Connection connection;
- private IsolationLevel isolationLevel = new IsolationLevel();
- private boolean commmitted = false;
- private boolean newTransaction = false;
- public JtaTransaction(UserTransaction utx, DataSource ds, int isolationLevel) throws TransactionException {
- // Check parameters
- userTransaction = utx;
- dataSource = ds;
- if (userTransaction == null) {
- throw new TransactionException("JtaTransaction initialization failed. UserTransaction was null.");
- }
- if (dataSource == null) {
- throw new TransactionException("JtaTransaction initialization failed. DataSource was null.");
- }
- this.isolationLevel.setIsolationLevel(isolationLevel);
- }
- private void init() throws TransactionException, SQLException {
- // Start JTA Transaction
- try {
- newTransaction = userTransaction.getStatus() == Status.STATUS_NO_TRANSACTION;
- if (newTransaction) {
- userTransaction.begin();
- }
- } catch (Exception e) {
- throw new TransactionException("JtaTransaction could not start transaction. Cause: ", e);
- }
- // Open JDBC Connection
- connection = dataSource.getConnection();
- if (connection == null) {
- throw new TransactionException("JtaTransaction could not start transaction. Cause: The DataSource returned a null connection.");
- }
- // Isolation Level
- isolationLevel.applyIsolationLevel(connection);
- // AutoCommit
- if (connection.getAutoCommit()) {
- connection.setAutoCommit(false);
- }
- // Debug
- if (connectionLog.isDebugEnabled()) {
- connection = ConnectionLogProxy.newInstance(connection);
- }
- }
- public void commit() throws SQLException, TransactionException {
- if (connection != null) {
- if (commmitted) {
- throw new TransactionException("JtaTransaction could not commit because this transaction has already been committed.");
- }
- try {
- if (newTransaction) {
- userTransaction.commit();
- }
- } catch (Exception e) {
- throw new TransactionException("JtaTransaction could not commit. Cause: ", e);
- }
- commmitted = true;
- }
- }
- public void rollback() throws SQLException, TransactionException {
- if (connection != null) {
- if (!commmitted) {
- try {
- if (userTransaction != null) {
- if (newTransaction) {
- userTransaction.rollback();
- } else {
- userTransaction.setRollbackOnly();
- }
- }
- } catch (Exception e) {
- throw new TransactionException("JtaTransaction could not rollback. Cause: ", e);
- }
- }
- }
- }
- public void close() throws SQLException, TransactionException {
- if (connection != null) {
- try {
- isolationLevel.restoreIsolationLevel(connection);
- } finally {
- connection.close();
- connection = null;
- }
- }
- }
- public Connection getConnection() throws SQLException, TransactionException {
- if (connection == null) {
- init();
- }
- return connection;
- }
- }
自定义事务
- public class ExternalTransaction implements Transaction {
- private static final Log connectionLog = LogFactory.getLog(Connection.class);
- private DataSource dataSource;
- private boolean defaultAutoCommit;
- private boolean setAutoCommitAllowed;
- private Connection connection;
- private IsolationLevel isolationLevel = new IsolationLevel();
- public ExternalTransaction(DataSource ds, boolean defaultAutoCommit, boolean setAutoCommitAllowed, int isolationLevel) throws TransactionException {
- // Check Parameters
- dataSource = ds;
- if (dataSource == null) {
- throw new TransactionException("ExternalTransaction initialization failed. DataSource was null.");
- }
- this.defaultAutoCommit = defaultAutoCommit;
- this.setAutoCommitAllowed = setAutoCommitAllowed;
- this.isolationLevel.setIsolationLevel(isolationLevel);
- }
- private void init() throws SQLException, TransactionException {
- // Open JDBC Transaction
- connection = dataSource.getConnection();
- if (connection == null) {
- throw new TransactionException("ExternalTransaction could not start transaction. Cause: The DataSource returned a null connection.");
- }
- // Isolation Level
- isolationLevel.applyIsolationLevel(connection);
- // AutoCommit
- if (setAutoCommitAllowed) {
- if (connection.getAutoCommit() != defaultAutoCommit) {
- connection.setAutoCommit(defaultAutoCommit);
- }
- }
- // Debug
- if (connectionLog.isDebugEnabled()) {
- connection = ConnectionLogProxy.newInstance(connection);
- }
- }
- public void commit() throws SQLException, TransactionException {
- }
- public void rollback() throws SQLException, TransactionException {
- }
- public void close() throws SQLException, TransactionException {
- if (connection != null) {
- try {
- isolationLevel.restoreIsolationLevel(connection);
- } finally {
- connection.close();
- connection = null;
- }
- }
- }
- public Connection getConnection() throws SQLException, TransactionException {
- if (connection == null) {
- init();
- }
- return connection;
- }
- }
可以看出TransactionConfig封装了Connection的细节部分,每个TransactionConfig实例绑定着一个Connection,这个Connection是从ds中获取到的。
有了配置文件,事务状态,隔离级别,事务异常,就开始使用了,TrasactionManger就是来调度所有这些上下文Config。其中有begin,commit,end主要的三个方法,其中begin是新建一个Config并植入session对象且定义当前session的事务状态,commit则提交当前session的事务,end显然就结束当前session事务。如下:
- public class TransactionManager {
- private TransactionConfig transactionConfig;
- private boolean forceCommit;
- private Throttle txThrottle;
- public TransactionManager(TransactionConfig transactionConfig) {
- this.transactionConfig = transactionConfig;
- this.txThrottle = new Throttle(transactionConfig.getMaximumConcurrentTransactions());
- }
- public void begin(SessionScope session) throws SQLException, TransactionException {
- begin(session, IsolationLevel.UNSET_ISOLATION_LEVEL);
- }
- public void begin(SessionScope session, int transactionIsolation) throws SQLException, TransactionException {
- Transaction trans = session.getTransaction();
- TransactionState state = session.getTransactionState();
- if (state == TransactionState.STATE_STARTED) {
- throw new TransactionException("TransactionManager could not start a new transaction. " +
- "A transaction is already started.");
- } else if (state == TransactionState.STATE_USER_PROVIDED) {
- throw new TransactionException("TransactionManager could not start a new transaction. " +
- "A user provided connection is currently being used by this session. " +
- "The calling .setUserConnection (null) will clear the user provided transaction.");
- }
- txThrottle.increment();
- try {
- trans = transactionConfig.newTransaction(transactionIsolation);
- session.setCommitRequired(false);
- } catch (SQLException e) {
- txThrottle.decrement();
- throw e;
- } catch (TransactionException e) {
- txThrottle.decrement();
- throw e;
- }
- session.setTransaction(trans);
- session.setTransactionState(TransactionState.STATE_STARTED);
- }
- public void commit(SessionScope session) throws SQLException, TransactionException {
- Transaction trans = session.getTransaction();
- TransactionState state = session.getTransactionState();
- if (state == TransactionState.STATE_USER_PROVIDED) {
- throw new TransactionException("TransactionManager could not commit. " +
- "A user provided connection is currently being used by this session. " +
- "You must call the commit() method of the Connection directly. " +
- "The calling .setUserConnection (null) will clear the user provided transaction.");
- } else if (state != TransactionState.STATE_STARTED && state != TransactionState.STATE_COMMITTED ) {
- throw new TransactionException("TransactionManager could not commit. No transaction is started.");
- }
- if (session.isCommitRequired() || forceCommit) {
- trans.commit();
- session.setCommitRequired(false);
- }
- session.setTransactionState(TransactionState.STATE_COMMITTED);
- }
- public void end(SessionScope session) throws SQLException, TransactionException {
- Transaction trans = session.getTransaction();
- TransactionState state = session.getTransactionState();
- if (state == TransactionState.STATE_USER_PROVIDED) {
- throw new TransactionException("TransactionManager could not end this transaction. " +
- "A user provided connection is currently being used by this session. " +
- "You must call the rollback() method of the Connection directly. " +
- "The calling .setUserConnection (null) will clear the user provided transaction.");
- }
- try {
- if (trans != null) {
- try {
- if (state != TransactionState.STATE_COMMITTED) {
- if (session.isCommitRequired() || forceCommit) {
- trans.rollback();
- session.setCommitRequired(false);
- }
- }
- } finally {
- session.closePreparedStatements();
- trans.close();
- }
- }
- } finally {
- if (state != TransactionState.STATE_ENDED) {
- txThrottle.decrement();
- }
- session.setTransaction(null);
- session.setTransactionState(TransactionState.STATE_ENDED);
- }
- }
- public DataSource getDataSource() {
- return transactionConfig.getDataSource();
- }
- public void setDataSource(DataSource ds) {
- transactionConfig.setDataSource(ds);
- }
- public boolean isForceCommit() {
- return forceCommit;
- }
- public void setForceCommit(boolean forceCommit) {
- this.forceCommit = forceCommit;
- }
- }
所以关于事务的上下文控制在ibatis的scope包里面了。