All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gu.sql2java.BaseTableManager Maven / Gradle / Ivy

There is a newer version: 5.3.3
Show newest version
package gu.sql2java;

import java.lang.reflect.Array;
import java.nio.ByteBuffer;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Predicate;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import gu.sql2java.ForeignKeyMetaData.ForeignKeyRule;
import gu.sql2java.Manager.AutoKeyRetrieveType;
import gu.sql2java.exception.DaoException;
import gu.sql2java.exception.DataAccessException;
import gu.sql2java.exception.DataRetrievalException;
import gu.sql2java.exception.ObjectRetrievalException;
import gu.sql2java.exception.OptimisticLockingException;
import gu.sql2java.exception.RuntimeDaoException;

import static com.google.common.base.Preconditions.*;
import static gu.sql2java.SimpleLog.*;
import static gu.sql2java.Managers.*;

/**
 * implementation of {@link TableManager} 
 * @author guyadong
 *
 * @param   java bean type
 */
class BaseTableManager implements TableManager,Constant{
    protected final RowMetaData metaData;
    /** lazy load */
	private volatile Map> foreignKeyDeleteListeners;
	/** lazy load */
	private volatile ListenerContainer listenerContainer;
	/** lazy load */
	private volatile String generatedkeyStatement;
	private static boolean debug = false;
    protected BaseTableManager(String tablename){
    	metaData = RowMetaData.getMetaData(tablename);
    }

    protected ListenerContainer getListenerContainer() {
		// double checking
		if(listenerContainer == null){
			synchronized (this) {
				if(listenerContainer == null){
					listenerContainer = new ListenerContainer();
				}
			}
		}
		return listenerContainer;
	}

    /**
     * @return map with foreignKey name TO TableListener
     */
    protected Map> getForeignKeyDeleteListeners(){
    	// double checking
    	if(foreignKeyDeleteListeners == null){
    		synchronized (this) {
    			if(foreignKeyDeleteListeners == null){
    				LinkedHashMap> map = Maps.newLinkedHashMap();
    				for(ForeignKeyMetaData fk : metaData.getForeignKeysForListener()){
    					map.put(fk.name, new DeleteRuleListener(fk.name));
    				}
    				foreignKeyDeleteListeners = Collections.unmodifiableMap(map);
    			}
			}
    	}
    	return foreignKeyDeleteListeners;
    }
	private String getGeneratedkeyStatement(Connection c){
		// double check
		if(generatedkeyStatement == null){
			synchronized (this) {
				if(generatedkeyStatement == null){
					generatedkeyStatement = checkNotNull(getManager().getGeneratedkeyStatement(c),"INVALID generatedkeyStatement")
						.replaceAll("", metaData.tablename )
						.replaceAll("", metaData.columnNameOf(metaData.autoincrementColumnId));
					if(debug){
						log("generatedkeyStatement={}",generatedkeyStatement);
					}
				}
			}
		}
		return generatedkeyStatement;		
	}

	/**
     * Creates a new B instance.
     *
     * @return the new B instance
     */
	@SuppressWarnings("unchecked")
    protected final B createBean()
    {
    	try {
			return (B) metaData.beanType.newInstance();
		} catch (Exception e) {
			Throwables.throwIfUnchecked(e);
			throw new RuntimeException(e);
		}
    }
	/**
	 * Creates a new B instance.
	 * @param primaryValues values of primary keys
	 * @return B instance
	 */
	protected final B createBean(Object... primaryValues)
    {
		checkArgument(primaryValues != null && primaryValues.length== metaData.primaryKeyNames.length,"INVALID primaryValues");
		B bean = createBean();
		int[] pkIds = metaData.primaryKeyIds;
		for(int i=0;iboolean hasNullPk(T bean){
		return (null == bean || hasNull(bean.primaryValues()));
	}
	private void prepareAutoincrement( Connection c,PreparedStatement ps, B bean) throws SQLException
    {
        if (!bean.isModified(metaData.autoincrementColumnId))
        {
            PreparedStatement ps2 = null;
            ResultSet rs = null;
            try {
            	AutoKeyRetrieveType retrieveType = getManager().getGeneratedkeyRetrieveType(c);
                if(AutoKeyRetrieveType.auto.equals(retrieveType)){
                	rs = ps.getGeneratedKeys();
                }else{
                    ps2 = c.prepareStatement(getGeneratedkeyStatement(c));
                	rs = ps2.executeQuery();
                }
                if(rs.next()) {
                    setColumnValue(bean, 
                    		metaData.autoincrementColumnId, 
                    		Manager.getObject(rs,1,metaData.columnTypes.get(metaData.autoincrementColumnId)));
                } else {
                    throw new IllegalStateException(logString("ATTENTION: Could not retrieve generated key!(retrieveType:{})",retrieveType));
                }
            } finally {
               getManager().close(ps2, rs);
            }
        }
    }
	
    //13
    /**
     * Insert the B bean into the database.
     * 
     * @param bean the B bean to be saved
     * @return the inserted bean
     * @throws RuntimeDaoException
     */
    protected B insert(final B bean)
    {
        // mini checks
        if (null == bean || !bean.isModified()) {
            return bean; 
        }
        if (!bean.isNew()){
            return this.update(bean);
        }

        Connection c = null;
        PreparedStatement ps = null;

		try
        {
            c = this.getConnection();
            final AutoKeyRetrieveType retrieveType = getManager().getGeneratedkeyRetrieveType(c);
            //-------------writePreInsert
            if(metaData.autoincrementColumnId >= 0 
            		&& AutoKeyRetrieveType.before.equals(retrieveType)){
        		prepareAutoincrement(c, null, bean);
        	}
            //------------/writePreInsert
            // listener callback
            getListenerContainer().beforeInsert(bean);
            StringBuilder sql = new StringBuilder("INSERT into " + metaData.tablename + " (");
            List modifiedList = Lists.newArrayList(Iterables.filter(metaData.columnNames,new Predicate() {

    			@Override
    			public boolean apply(String input) {
    				return bean.isModified(input);
    			}
    		}));
            String fields=Joiner.on(",").join(modifiedList);
            // fields
            sql.append(fields);
            sql.append(") values (");
            // values
            sql.append(fields.replaceAll("[^,]+", "?"));
            sql.append(")");
            if(debug){
            	log("insert : " + sql.toString());
            }
            if(metaData.autoincrementColumnId >= 0 && AutoKeyRetrieveType.auto.equals(retrieveType)){
            	ps = c.prepareStatement(sql.toString(), Statement.RETURN_GENERATED_KEYS);
            }else{
                ps = c.prepareStatement(sql.toString(), ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
            }
            
            fillPreparedStatement(ps, bean, SEARCH_EXACT, true);

            ps.executeUpdate();
            //------------------writePostInsert
            if(metaData.autoincrementColumnId >= 0 && !AutoKeyRetrieveType.before.equals(retrieveType)){
        		prepareAutoincrement(c, ps, bean);
        	}
            //-------------------/writePostInsert
            bean.setNew(false);
            bean.resetIsModified();
            // listener callback
            getListenerContainer().afterInsert(bean);
            return bean;
        }
        catch(SQLException e)
        {
            throw new RuntimeDaoException(new DataAccessException(e));
        }
        finally
        {
            // listener callback
            getListenerContainer().done();
            getManager().close(ps);
            freeConnection(c);
        }
    }
    
    private static final Function SQL_FUN1 = new Function(){

		@Override
		public String apply(String input) {
			return input + "=?";
		}};
    //14
    /**
     * Update the B bean record in the database according to the changes.
     *
     * @param bean the B bean to be updated
     * @return the updated bean
     * @throws RuntimeDaoException
     */
    protected B update(final B bean) throws RuntimeDaoException
    {
        // mini checks
        if (null == bean || !bean.isModified()) {
            return bean;
        }
        if (bean.isNew()){
            return this.insert(bean);
        }

        Connection c = null;
        PreparedStatement ps = null;

        try
        {
            c = this.getConnection();

            // listener callback
            getListenerContainer().beforeUpdate(bean); 
            Object oldLockValue = null;
            if(metaData.lockColumnType != null){
            	oldLockValue = bean.getValue(metaData.lockColumnName);
        		// lockColumnType is String or Long
        		if(String.class == metaData.lockColumnType){
        			bean.setValue(metaData.lockColumnName,String.valueOf(System.currentTimeMillis())); 
        		}else if(Long.class ==metaData.lockColumnType){
        			bean.setValue(metaData.lockColumnName,System.currentTimeMillis()); 
        		}else{
        			throw new RuntimeException(logString("INVALID LOCK COLUMN TYPE:{},String or Long required",metaData.lockColumnType));
        		}
            }
            StringBuilder sql = new StringBuilder("UPDATE " + metaData.tablename + " SET ");
            List modified = Lists.newArrayList(Iterables.filter(metaData.columnNames,new Predicate() {

    			@Override
    			public boolean apply(String input) {
    				return bean.isModified(input);
    			}
    		}));
            sql.append(Joiner.on(",").join(Lists.transform(modified,SQL_FUN1)));

            sql.append(" WHERE ");
            sql.append(Joiner.on(" AND ").join(
	            Lists.transform(Arrays.asList(metaData.primaryKeyNames), SQL_FUN1)));
            if(metaData.lockColumnType != null){
            	if(metaData.primaryKeyNames.length > 0){
            		sql.append(" AND ");
            	}
            	checkArgument(metaData.lockColumnName != null, "NOT DEFINED lock column name");
            	sql.append(metaData.lockColumnName + "=?");
            }
            if(debug){
            	log("update : " + sql.toString());
            }
            ps = c.prepareStatement(sql.toString(),
                                    ResultSet.TYPE_SCROLL_INSENSITIVE,
                                    ResultSet.CONCUR_READ_ONLY);

            int dirtyCount = this.fillPreparedStatement(ps, bean, SEARCH_EXACT,true);

            if (dirtyCount == 0) {
            	if(debug){
            		log("The bean to look is not initialized... do not update.");
            	}
                return bean;
            }
            int[] pkIds = metaData.primaryKeyIds;
            for(int i = 0; i {
        final List list;
        public ListAction() {
            list=new LinkedList<>();
        }

        public List getList() {
            return list;
        }

        @Override
        public void call(B bean) {
            list.add(bean);
        }

    }
    
    @Override
    public int countAll()throws RuntimeDaoException{
        return this.countWhere("");
    }

    @Override
    public int countUsingTemplate(B bean)throws RuntimeDaoException{
        return this.countUsingTemplate(bean, SEARCH_EXACT);
    }

    @Override
    public int deleteAll()throws RuntimeDaoException{
        return this.deleteByWhere("");
    }

    @Override
    public B[] loadAll()throws RuntimeDaoException{
        return this.loadByWhere(null);
    }

    @Override
    public int loadAll(TableManager.Action action)throws RuntimeDaoException{
        return this.loadByWhere(null, action);
    }

    @SuppressWarnings("unchecked")
	@Override
    public B[] loadAll(int startRow, int numRows)throws RuntimeDaoException{
        return this.loadByWhereAsList(null, null, startRow, numRows).toArray((B[]) Array.newInstance(metaData.beanType, 0));
    }

    @Override
    public int loadAll(int startRow, int numRows, TableManager.Action action)throws RuntimeDaoException{
        return this.loadByWhereForAction(null, null, startRow, numRows, action);
    }

    @Override
    public List loadAllAsList()throws RuntimeDaoException{
        return this.loadUsingTemplateAsList(null,1, -1, SEARCH_EXACT);
    }

    @Override
    public List loadAllAsList(int startRow, int numRows)throws RuntimeDaoException{
        return this.loadUsingTemplateAsList(null, startRow, numRows, SEARCH_EXACT);
    }

    @Override
	public B loadByPrimaryKey(B bean)throws RuntimeDaoException{
		return bean==null ? null : loadByPrimaryKey(bean.primaryValues());
	}

	@Override
	public B loadByPrimaryKeyChecked(B bean)throws RuntimeDaoException,ObjectRetrievalException{
	    return loadByPrimaryKeyChecked(checkNotNull(bean,"bean is null").primaryValues());
	}
	@Override
	public final B loadByPrimaryKeyChecked(Object ...keys)throws RuntimeDaoException,ObjectRetrievalException{
		// {{check parameters
		if(metaData.primaryKeyNames.length == 0){
			throw new UnsupportedOperationException();
		}
	    String[] pkNames = metaData.primaryKeyNames;
	    int[] pkIds = metaData.primaryKeyIds;
		if(hasNull(keys)){
			throw new ObjectRetrievalException(new NullPointerException("primary key must not be null"));
		}
		checkArgument(keys.length == pkNames.length,
				"INVALID ARGUMENT NUM, %s required",pkNames.length);
	    for(int i=0; i type = metaData.columnTypeOf(pkIds[i]);
	    	checkArgument(type.isAssignableFrom(key.getClass()),
	    			"INVALID type for pk %s,%s required",pkNames[i],type.getName());
	    }
	 // }}check parameters
	    return doLoadByPrimaryKeyChecked(keys);
	}
	
	protected B doLoadByPrimaryKeyChecked(Object ...keys)throws RuntimeDaoException,ObjectRetrievalException{
		if(metaData.primaryKeyNames.length == 0){
			throw new UnsupportedOperationException();
		}
	    String[] pkNames = metaData.primaryKeyNames;
	    int[] pkIds = metaData.primaryKeyIds;
		if(hasNull(keys)){
			throw new ObjectRetrievalException(new NullPointerException("primary key must not be null"));
		}
		checkArgument(keys.length == pkNames.length,
				"INVALID ARGUMENT NUM, %s required",pkNames.length);
	    for(int i=0; i type = metaData.columnTypeOf(pkIds[i]);
	    	checkArgument(type.isAssignableFrom(key.getClass()),
	    			"INVALID type for pk %s,%s required",pkNames[i],type.getName());
	    }
	    Connection c = null;
	    PreparedStatement ps = null;
	    try
	    {
	        c = this.getConnection();
	        StringBuilder sql = new StringBuilder("SELECT " + metaData.columnFields + " FROM " + metaData.tablename + " WHERE ");
	        sql.append(Joiner.on(" AND ").join(
		            Lists.transform(Arrays.asList(pkNames), SQL_FUN1)));
	        if(debug){
	        	log("LOAD BY PK: " + sql.toString());
	        }
	        ps = c.prepareStatement(sql.toString(),
	                                ResultSet.TYPE_SCROLL_INSENSITIVE,
	                                ResultSet.CONCUR_READ_ONLY);
			for(int i = 0; i pReturn = this.loadByPreparedStatementAsList(ps,null,1,-1);
	        if (1 == pReturn.size()) {
	            return pReturn.get(0);
	        } else {
	            throw new ObjectRetrievalException();
	        }
	    }
	    catch(ObjectRetrievalException e)
	    {
	        throw e;
	    }
	    catch(SQLException e)
	    {
	        throw new RuntimeDaoException(new DataRetrievalException(e));
	    }
	    finally
	    {
	        this.getManager().close(ps);
	        this.freeConnection(c);
	    }
	}

	@Override
	public B loadByPrimaryKey(Object ...keys)throws RuntimeDaoException{
	    try{
	        return loadByPrimaryKeyChecked(keys);
	    }catch(ObjectRetrievalException e){
	        // not found
	        return null;
	    }
	}

	protected  List loadByPks(Collection keys){
		checkState(metaData.primaryKeyCount == 1,"UNSUPPORTED OPERATION");
	    if(null == keys){
	        return Collections.emptyList();
	    }
	    ArrayList list = new ArrayList(keys.size());
	    for(K key:keys){
	        list.add(loadByPrimaryKey(key));
	    }
	    return list;
	}
	
	@SuppressWarnings("unchecked")
	protected  List loadByPks(K... keys){
	    if(null == keys){
	    	return Collections.emptyList();
	    }
		return loadByPks(Arrays.asList(keys));
	}

	@Override
    public boolean existsByPrimaryKey(B bean)throws RuntimeDaoException{
    	return null == bean ? false : existsPrimaryKey(bean.primaryValues());
    }
    @Override
    public B checkDuplicate(B bean)throws RuntimeDaoException,ObjectRetrievalException{
        if(existsByPrimaryKey(bean)){
            throw new ObjectRetrievalException("Duplicate entry ("+ bean.primaryValues() +") for key 'PRIMARY'");
        }
        return bean;   
    }
    @Override
    public final boolean existsPrimaryKey(Object ...keys)throws RuntimeDaoException{
    	// {{check parameters
    	if(metaData.primaryKeyNames.length == 0){
    		throw new UnsupportedOperationException();
    	}
    	String[] pkNames = metaData.primaryKeyNames;
    	int[] pkIds = metaData.primaryKeyIds;
    	if(null == keys || hasNull(keys)){
    		return false;
    	}
    	checkArgument(keys.length == pkNames.length,
    			"INVALID ARGUMENT NUM, %s required",pkNames.length);
    	for(int i=0; i type = metaData.columnTypeOf(pkIds[i]);
    		checkArgument(type.isInstance(key),
    				"INVALID type for pk %s,%s required",pkNames[i],type.getName());
    	}
    	// }}check parameters
    	return doExistsPrimaryKey(keys);
    }
    protected boolean doExistsPrimaryKey(Object ...keys)throws RuntimeDaoException{
        String[] pkNames = metaData.primaryKeyNames;
        int[] pkIds = metaData.primaryKeyIds;
        Connection c = null;
        PreparedStatement ps = null;
        try{
            c = this.getConnection();
            StringBuilder sql = new StringBuilder("SELECT COUNT(*) AS MCOUNT FROM "  + metaData.tablename + " WHERE ");
            sql.append(Joiner.on(" AND ").join(Lists.transform(Arrays.asList(pkNames),SQL_FUN1)));
            if(debug){
            	log("ExistsPrimaryKey: " + sql);
            }
            ps = c.prepareStatement(sql.toString(),
                                    ResultSet.TYPE_SCROLL_INSENSITIVE,
                                    ResultSet.CONCUR_READ_ONLY);

			for(int i = 0; iT checkDuplicateByPk(T primaryKeyValue)throws ObjectRetrievalException{
    	if(metaData.primaryKeyNames.length != 1){
    		throw new UnsupportedOperationException();
    	}
	    if(existsPrimaryKey(primaryKeyValue)){
	        throw new ObjectRetrievalException("Duplicate entry '"+ primaryKeyValue +"' for key 'PRIMARY'");
	    }
	    return primaryKeyValue;
	}

	@Override
    public B[] loadByWhere(String where)throws RuntimeDaoException{
        return this.loadByWhere(where, (int[])null);
    }

    @Override
    public int loadByWhere(String where, TableManager.Action action)throws RuntimeDaoException{
        return this.loadByWhere(where, null, action);
    }

    @Override
    public B[] loadByWhere(String where, int[] fieldList)throws RuntimeDaoException{
        return this.loadByWhere(where, fieldList, 1, -1);
    }

    @Override
    public int loadByWhere(String where, int[] fieldList, TableManager.Action action)throws RuntimeDaoException{
        return this.loadByWhere(where, fieldList, 1, -1, action);
    }

    @SuppressWarnings("unchecked")
    @Override
    public B[] loadByWhere(String where, int[] fieldList, int startRow, int numRows)throws RuntimeDaoException{
        return this.loadByWhereAsList(where, fieldList, startRow, numRows).toArray((B[])Array.newInstance(metaData.beanType,0));
    }

    @Override
    public int loadByWhere(String where, int[] fieldList, int startRow, int numRows,
            TableManager.Action action)throws RuntimeDaoException{
        return this.loadByWhereForAction(where, fieldList, startRow, numRows, action);
    }

    @Override
    public List loadByWhereAsList(String where)throws RuntimeDaoException{
        return this.loadByWhereAsList(where, null, 1, -1);
    }

    @Override
    public List loadByWhereAsList(String where, int[] fieldList)throws RuntimeDaoException{
        return this.loadByWhereAsList(where, fieldList, 1, -1);
    }

    @Override
    public List loadByWhereAsList(String where, int[] fieldList, int startRow, int numRows)throws RuntimeDaoException{
        ListAction action = new ListAction();
        loadByWhereForAction(where, fieldList, startRow, numRows, action);              
        return action.getList();
    }

    @Override
    public int loadByWhereForAction(String where, int[] fieldList, int startRow, int numRows,TableManager.Action action)throws RuntimeDaoException{
        String sql=createSelectSql(fieldList, where);
        return this.loadBySqlForAction(sql, null, fieldList, startRow, numRows, action);
    }

    @Override
    public B[] loadUsingTemplate(B bean)throws RuntimeDaoException{
        return this.loadUsingTemplate(bean, 1, -1, SEARCH_EXACT);
    }

    @Override
    public int loadUsingTemplate(B bean, TableManager.Action action)throws RuntimeDaoException{
        return this.loadUsingTemplate(bean, null, 1, -1, SEARCH_EXACT, action);
    }

    @Override
    public B[] loadUsingTemplate(B bean, int startRow, int numRows)throws RuntimeDaoException{
        return this.loadUsingTemplate(bean, startRow, numRows, SEARCH_EXACT);
    }

    @Override
    public int loadUsingTemplate(B bean, int startRow, int numRows,
            TableManager.Action action)throws RuntimeDaoException{
        return this.loadUsingTemplate(bean, null, startRow, numRows,SEARCH_EXACT, action);
    }

    @SuppressWarnings("unchecked")
    @Override
    public B[] loadUsingTemplate(B bean, int startRow, int numRows, int searchType)throws RuntimeDaoException{
        return this.loadUsingTemplateAsList(bean, startRow, numRows, searchType).toArray((B[])Array.newInstance(metaData.beanType,0));
    }

    @Override
    public List loadUsingTemplateAsList(B bean)throws RuntimeDaoException{
        return this.loadUsingTemplateAsList(bean, 1, -1, SEARCH_EXACT);
    }

    @Override
    public List loadUsingTemplateAsList(B bean, int startRow, int numRows)throws RuntimeDaoException{
        return this.loadUsingTemplateAsList(bean, startRow, numRows, SEARCH_EXACT);
    }

    @Override
    public List loadUsingTemplateAsList(B bean, int startRow, int numRows, int searchType)throws RuntimeDaoException{
        ListAction action = new ListAction();
        loadUsingTemplate(bean,null,startRow,numRows,searchType, action);
        return action.getList();
    }
    //18
    @Override
    public B loadUniqueUsingTemplate(B bean)
    {
    	try {
    		return loadUniqueUsingTemplateChecked(bean);	
		} catch (ObjectRetrievalException e) {
			return null;
		}
     }
    //18-1
    @Override
    public B loadUniqueUsingTemplateChecked(B bean) throws ObjectRetrievalException
    {
         List beans = this.loadUsingTemplateAsList(bean);
         switch(beans.size()){
         case 0:
             throw new ObjectRetrievalException("Not found element !!");
         case 1:
             return beans.get(0);
         default:
             throw new RuntimeDaoException(new ObjectRetrievalException("More than one element !!"));
         }
     }
    
    @Override
    public int loadUsingTemplate(B bean, int[] fieldList, int startRow, int numRows,int searchType, Action action)
    {
        StringBuilder sqlWhere = new StringBuilder("");
        String sql=createSelectSql(fieldList,
        		this.fillWhere(sqlWhere, bean, searchType) > 0 ? " WHERE "+ sqlWhere.toString() : null);
        PreparedStatement ps = null;
        Connection connection = null;
        try {
            connection = this.getConnection();
            if(debug){
            	log("loadUsingTemplate:" + sql);
            }
            ps = connection.prepareStatement(sql,
                    ResultSet.TYPE_FORWARD_ONLY,
                    ResultSet.CONCUR_READ_ONLY);
            this.fillPreparedStatement(ps, bean, searchType,false);
            return this.loadByPreparedStatement(ps, fieldList, startRow, numRows, action);
        } catch (DaoException e) {
            throw new RuntimeDaoException(e);
        }catch (SQLException e) {
            throw new RuntimeDaoException(new DataAccessException(e));
        } finally {
            this.getManager().close(ps);
            this.freeConnection(connection);
        }
    }
    /**
     * @param  bean type of foreign table 
     * @param left
     * @param columnsMap
     * @param startRow
     * @param numRows
     * @return list of B or empty list
     */
    private 
	List loadByForeignKeyAsList(F left,MapcolumnsMap,int startRow, int numRows)
	{
	    if(null == left){
	        return Collections.emptyList();
	    }
		checkArgument(columnsMap != null && !columnsMap.isEmpty(),"columnsMap is null or empty");
	    B bean = createBean().copy(left, columnsMap);
	    return loadUsingTemplateAsList(bean,startRow,numRows);
	}
    /**
     * @param fkName
     * @param left
     * @param startRow
     * @param numRows
     * @param  bean type of foreign table
     * @return list of B or empty list
     */
    protected 
	List loadByForeignKeyAsList(String fkName,F left,int startRow, int numRows)
	{
	    ImmutableBiMap columnsMap = metaData.foreignKeyIdMapOf(fkName).inverse();
	    return loadByForeignKeyAsList(left,columnsMap,startRow,numRows);
	}

	@Override
    public B save(B bean)throws RuntimeDaoException{
        if(null != bean){
            if (bean.isNew()) {
                this.insert(bean);
            } else {
                this.update(bean);
            }
        }
        return bean;
    }
    
    @Override
    public B[] save(B[] beans)throws RuntimeDaoException{
        if(null != beans){
            for (B bean : beans) 
            {
                this.save(bean);
            }
        }
        return beans;
    }

    @Override
    public > C save(C beans)throws RuntimeDaoException{
        if(null != beans){
            for (B bean : beans) 
            {
                this.save(bean);
            }
        }
        return beans;
    }
    
    @Override
    public > C saveAsTransaction(final C beans)throws RuntimeDaoException{
        return this.runAsTransaction(new Callable(){
            @Override
            public C call() throws Exception {
                return save(beans);
            }});
    }

    @Override
    public B[] saveAsTransaction(final B[] beans)throws RuntimeDaoException{
        return this.runAsTransaction(new Callable(){
            @Override
            public B[] call() throws Exception {
                return save(beans);
            }});
    }

    private String checkWhere(String where){
    	where = MoreObjects.firstNonNull(where, "").trim();
        checkArgument(where.isEmpty() || where.toUpperCase().startsWith("WHERE "),
        		"WHERE expression must start with 'WHERE'(case insensitive)");
        return where;
    }
    
    @SuppressWarnings("unchecked")
	@Override
    public  List loadColumnAsList(String column,boolean distinct,String where,int startRow,int numRows)throws RuntimeDaoException{
        int columnId = metaData.columnIDOf(column);
        checkArgument(columnId>=0,"INVALID column name %s",column);
        String sql = String.format("SELECT %s " + metaData.columnNameOf(columnId) + " FROM %s %s",
                distinct ? "DISTINCT" : "",
                metaData.tablename,
                checkWhere(where));
        return runSqlAsList((Class)metaData.columnTypes.get(columnId), sql);
    }
    /**
     * generate SQL query(SELECT) statement,such as: 'SELECT id,name from mytable WHERE id=1'
     * @param fieldList
     * @param where where condition expression statement that start with 'WHERE',or {@code null},or empty string
     * @return SQL statement string
     * @throws IllegalArgumentException where condition expression don't start with 'WHERE'
     */
    private String createSelectSql(int[] fieldList, String where){
        StringBuffer sql = new StringBuffer(128);
        String fullFields = metaData.columnFullFields;
        if(null == fieldList || 0 == fieldList.length) {
            sql.append("SELECT ").append(fullFields);
        } else{
            sql.append("SELECT ");
            String[] names=fullFields.split(",");
            for(int i = 0; i < fieldList.length; ++i){
                if(i > 0) {
                    sql.append(",");
                }
                sql.append(names[fieldList[i]]);
            }      
        }
        sql.append(" FROM " + this.metaData.tablename + " ");
        sql.append(checkWhere(where));
        return sql.toString();
    }

    //2.2

    @Override
    public int delete(B bean){
    	if(metaData.primaryKeyNames.length==0){
    		throw new UnsupportedOperationException();
    	}
        if(hasNullPk(bean)){
        	return 0;
        }
        Connection c = null;
        PreparedStatement ps = null;
        String [] pks = metaData.primaryKeyNames;
        int[] pkIds = metaData.primaryKeyIds;
        try
        {
            // listener callback
            getListenerContainer().beforeDelete(bean);
            c = this.getConnection();
            StringBuilder sql = new StringBuilder("DELETE  FROM " + metaData.tablename +" WHERE ");
            sql.append(Joiner.on(" AND ").join(Lists.transform(Arrays.asList(pks),SQL_FUN1)));
            if(debug){
            	log("deleteByPrimaryKey: " + sql);
            }
            ps = c.prepareStatement(sql.toString(),
                                    ResultSet.TYPE_SCROLL_INSENSITIVE,
                                    ResultSet.CONCUR_READ_ONLY);
            for(int i = 0; i0){
                // listener callback
                getListenerContainer().afterDelete(bean);
            }
            return rows;
        }
        catch(SQLException e)
        {
            throw new RuntimeDaoException(new DataAccessException(e));
        }
        finally
        {
            // listener callback
            getListenerContainer().done();
            getManager().close(ps);
            freeConnection(c);
        }
    }

    protected  int deleteByPks(Collection keys){
		checkState(metaData.primaryKeyCount == 1,"UNSUPPORTED OPERATION");
	    int count = 0;
	    if(null != keys){
	        for(K key :keys){
	            count += deleteByPrimaryKey(key);
	        }
	    }
	    return count;
	}

	@SuppressWarnings("unchecked")
	protected  int deleteByPks(K... keys){
	    if(null == keys){
	    	return 0;
	    }
	    return deleteByPks(Arrays.asList(keys));
	}

	@Override
	public int deleteByPrimaryKey(Object ...keys)throws RuntimeDaoException{
	    if(hasNull(keys)){
	    	return 0;
	    }
		checkArgument(keys.length == metaData.primaryKeyCount,"argument number mismatch with primary key number");
	    return delete(createBean(keys));
	}

	//2.4 

	@SuppressWarnings("unchecked")
	@Override
	public int delete(B... beans)throws RuntimeDaoException{
	    int count = 0;
	    if(null != beans){
	        for(B bean :beans){
	            count += delete(bean);
	        }
	    }
	    return count;
	}

	//2.5  

	@Override
	public int delete(Collection beans)throws RuntimeDaoException{
	    int count = 0;
	    if(null != beans){
	        for(B bean :beans){
	            count += delete(bean);
	        }
	    }
	    return count;
	}

	/**
     * Retrieves the T object referenced by fkName.
* @param fkName foreign key name.
* @param bean the B object to use * @param * @return the associated T bean or {@code null} if {@code bean} is {@code null} * @throws RuntimeDaoException */ protected T getReferencedBean(String fkName, B bean) throws RuntimeDaoException{ if(null == bean){ return null; } ForeignKeyMetaData foreignkey = checkNotNull(metaData.foreignKeys.get(fkName), "INVALID fkName %s for table %s", fkName, metaData.tablename); BaseTableManager foreignManager = baseManagerOf(foreignkey.foreignTable); T t = foreignManager.createBean().copy(bean, metaData.foreignKeyIdMapOf(fkName)); return foreignManager.loadByPrimaryKey(t); } /** * Associates the B object to the T object by fkName field.
* @param fkName see also {@link #getReferencedBean(String, BaseBean)} * @param bean the B object to use * @param beanToSet the T object to associate to the B bean * @param see also {@link #getReferencedBean(String, BaseBean)} * @return always beanToSet saved * @throws RuntimeDaoException */ protected T setReferencedBean(String fkName, B bean, T beanToSet) throws RuntimeDaoException{ if(null != bean){ ForeignKeyMetaData foreignkey = checkNotNull(metaData.foreignKeys.get(fkName), "INVALID fkName %s for table %s", fkName, metaData.tablename); TableManager foreignManager = managerOf(foreignkey.foreignTable); foreignManager.save(beanToSet); bean.copy(beanToSet, metaData.foreignKeyIdMapOf(fkName).inverse()); } return beanToSet; } /** * Retrieves imported T objects by fkIndex.
* @param * @param fkName foreign key name.
* @param bean the B object to use * @return the associated T beans or {@code null} if {@code bean} is {@code null} * @throws RuntimeDaoException */ @SuppressWarnings("unchecked") protected T[] getImportedBeans(String fkName,B bean) throws RuntimeDaoException{ ForeignKeyMetaData foreignkey =metaData.getImportedKey(fkName); RowMetaData foreignMetaData = RowMetaData.getMetaData(foreignkey.foreignTable); return getImportedBeansAsList(fkName,bean).toArray((T[]) Array.newInstance(foreignMetaData.beanType, 0)); } /** * Retrieves imported T objects by fkIndex.
* @param * @param fkName foreign key name.
* @param bean the B object to use * @return the associated T beans or {@code null} if {@code bean} is {@code null} * @throws RuntimeDaoException */ protected List getImportedBeansAsList(String fkName, B bean,int startRow, int numRows)throws RuntimeDaoException{ ForeignKeyMetaData foreignkey =metaData.getImportedKey(fkName); TableManager foreignManager = managerOf(foreignkey.ownerTable); return baseManagerOf(foreignManager).loadByForeignKeyAsList(fkName,bean, startRow, numRows); } /** * Retrieves imported T objects by ikIndex.
* @param fkname foreign key name.see also {@link #getImportedBeans(String, BaseBean)} * @param bean the B object to use * @param see also {@link #getImportedBeans(String, BaseBean)} * @return the associated T beans or {@code null} if {@code bean} is {@code null} * @throws RuntimeDaoException */ protected List getImportedBeansAsList(String fkName, B bean)throws RuntimeDaoException{ return getImportedBeansAsList(fkName,bean, 1, -1); } protected List getImportedBeansAsList(String fkName,Object...keys) throws RuntimeDaoException{ return getImportedBeansAsList(fkName,createBean(keys),1,-1); } @SuppressWarnings("unchecked") protected T[] getImportedBeans(String fkName,Object...keys) throws RuntimeDaoException{ ForeignKeyMetaData foreignkey =metaData.getImportedKey(fkName); RowMetaData foreignMetaData = RowMetaData.getMetaData(foreignkey.foreignTable); return getImportedBeansAsList(fkName,keys).toArray((T[]) Array.newInstance(foreignMetaData.beanType, 0)); } /** * Set the importedBeans associates to the bean by fkIndex
* @param fkName foreign key name. see also {@link #getImportedBeans(String, BaseBean)} * @param bean the bean object to use * @param importedBeans the T object to associate to bean * * @param see also {@link #getImportedBeans(String, BaseBean)} * @return importedBeans always * @throws RuntimeDaoException */ protected > C setImportedBeans(String fkName, B bean, C importedBeans)throws RuntimeDaoException{ if(null != importedBeans){ ForeignKeyMetaData foreignkey =metaData.getImportedKey(fkName); BaseTableManager foreignManager = baseManagerOf(foreignkey.ownerTable); for( T importBean : importedBeans ){ foreignManager.setReferencedBean(fkName , importBean,bean); } } return importedBeans; } /** * Set the importedBeans associates to the bean by {@code ikIndex}
* @param fkName foreign key name.see also {@link #getImportedBeans(String, BaseBean)} * @param bean the bean object to use * @param importedBeans the T object to associate to bean * * @param see also {@link #getImportedBeans(String, BaseBean)} * @return importedBeans always * @throws RuntimeDaoException */ protected T[] setImportedBeans(String fkName, B bean, T[] importedBeans) throws RuntimeDaoException{ if(null != importedBeans){ setImportedBeans(fkName, bean, Arrays.asList(importedBeans)); } return importedBeans; } /** * delete all imported beans by fkName * @param fkName foreign key name * @param bean * @return deleted row count or 0 if bean is null */ protected int deleteImportedBeans(String fkName,B bean) throws RuntimeDaoException{ if(bean == null){ return 0; } ForeignKeyMetaData foreignkey = metaData.getImportedKey(fkName); RowMetaData foreignMetaData = RowMetaData.getMetaData(foreignkey.foreignTable); BaseTableManager foreignManager = baseManagerOf(foreignkey.ownerTable); BaseBean tmpl = foreignManager.createBean().copy(bean,foreignMetaData.foreignKeyIdMapOf(fkName)); return foreignManager.deleteUsingTemplate(tmpl); } protected int deleteImportedBeans(String fkName, Map idValueMap) throws RuntimeDaoException{ B bean = createBean().copy(idValueMap); return deleteImportedBeans(fkName,bean); } protected int deleteImportedBeans(String fkName,Object...keys) throws RuntimeDaoException{ return deleteImportedBeans(fkName,createBean(keys)); } private Map makeIndexValueMap(String indexName,Object ...indexValues){ IndexMetaData indexMetaData = metaData.indices.get(indexName); checkArgument(null != indexMetaData,"INVALID index name %s",indexName); checkArgument(null != indexValues && indexValues.length == indexMetaData.columns.size(),"INVALID index value"); ImmutableMap.Builder builder = ImmutableMap.builder(); for(int i=0;i columnType = metaData.columnTypeOf(column); checkArgument(columnType.isInstance(value), "INVALID value type %s for %s,%s required",value.getClass(),column,columnType); builder.put(metaData.columnIDOf(column), value); } } return builder.build(); } //_____________________________________________________________________ // // USING INDICES //_____________________________________________________________________ /** * Retrieves a array of B bean using the index specified by keyIndex. * @param indexName name of index * @param indexValues key values of index * @return B array * @throws RuntimeDaoException * @see #loadByIndexAsList(int ,Object ...) */ @SuppressWarnings("unchecked") protected B[] loadByIndex(String indexName,Object ...keys)throws RuntimeDaoException{ return this.loadByIndexAsList(indexName,keys).toArray((B[])Array.newInstance(metaData.beanType,0)); } /** * Retrieves a list of B bean using the index specified by indexName. * @param indexName name of index
* @param indexValues key values of index * @return a list of B bean * @throws RuntimeDaoException */ protected List loadByIndexAsList(String indexName,Object ...indexValues)throws RuntimeDaoException{ Map map = makeIndexValueMap(indexName,indexValues); return loadUsingTemplateAsList(map); } protected final B loadUniqueByIndex(String indexName,Object ...indexValues)throws RuntimeDaoException{ return doLoadUniqueByIndex(indexName, indexValues); } protected B doLoadUniqueByIndex(String indexName,Object ...indexValues)throws RuntimeDaoException{ Map keys = makeIndexValueMap(indexName,indexValues); B bean = createBean().copy(keys); return loadUniqueUsingTemplate(bean); } protected final B loadUniqueByIndexChecked(String indexName,Object ...indexValues)throws ObjectRetrievalException{ if(hasNull(indexValues)){ throw new ObjectRetrievalException(new NullPointerException("index keys must not be null")); } return doLoadUniqueByIndexChecked(indexName, indexValues); } protected B doLoadUniqueByIndexChecked(String indexName,Object ...indexValues)throws ObjectRetrievalException{ Map keys = makeIndexValueMap(indexName,indexValues); B bean = createBean().copy(keys); return loadUniqueUsingTemplateChecked(bean); } /** * Deletes rows using key. * @param indexName name of index * @param indexValues key values of index * @return the number of deleted objects * @throws RuntimeDaoException */ protected int deleteByIndex(String indexName,Object ...indexValues)throws RuntimeDaoException{ Map map = makeIndexValueMap(indexName,indexValues); return deleteUsingTemplate(map); } @SuppressWarnings("unchecked") protected List loadViaJunctionAsList(String junctionTable,T linked, int startRow, int numRows){ BaseTableManager manager = baseManagerOf(junctionTable); return manager.loadViaJunctionTableAsList((Class)metaData.beanType, linked, startRow, numRows); } protected List loadViaJunctionAsList(String junctionTable,T linked){ return loadViaJunctionAsList(junctionTable,linked,1,-1); } private List loadUsingTemplateAsList(Mapkeys){ B bean = createBean().copy(keys); return loadUsingTemplateAsList(bean); } private int deleteUsingTemplate(Mapkeys){ B bean = createBean().copy(keys); return deleteUsingTemplate(bean); } protected List loadByIndexForIndices(String indexName,Collection indexs) { IndexMetaData indexMetaData = metaData.indices.get(indexName); checkArgument(null != indexMetaData,"INVALID index name %s",indexName); checkArgument(1 == indexMetaData.columns.size(),"column count of index must be 1"); checkArgument(indexMetaData.unique,"index must be unique"); if(null == indexs ){ return Collections.emptyList(); } List list = new ArrayList(indexs.size()); for(T key: indexs){ list.add(loadUniqueByIndex(indexName,key)); } return list; } @SuppressWarnings("unchecked") protected List loadByIndexForIndices(String indexName,T... indexs){ if(indexs == null || indexs.length == 0){ return Collections.emptyList(); } return loadByIndexForIndices(indexName,Arrays.asList(indexs)); } protected int deleteByIndexForIndices(String indexName,Collection indexs) { IndexMetaData indexMetaData = metaData.indices.get(indexName); checkArgument(null != indexMetaData,"INVALID index name %s",indexName); checkArgument(1 == indexMetaData.columns.size(),"column count of index must be 1"); if(null == indexs ){ return 0; } int count = 0; for(T key: indexs){ if(key != null){ count += deleteByIndex(indexName, key); } } return count; } @SuppressWarnings("unchecked") protected int deleteByIndexForIndices(String indexName,T... indexs){ if(indexs == null || indexs.length == 0){ return 0; } return deleteByIndexForIndices(indexName,Arrays.asList(indexs)); } private boolean checkPkValid(B bean) { if(metaData.primaryKeyNames.length ==0){ return false; } for(String name:metaData.primaryKeyNames){ if(! (bean.isInitialized(name) && null != bean.getValue(name))){ return false; } } return true; } //21 @Override public int deleteUsingTemplate(B bean) { if(bean == null || !bean.isModified()){ return 0; } if(checkPkValid(bean)){ return this.deleteByPrimaryKey(bean); } if( !getListenerContainer().isEmpty()){ DeleteBeanAction action=new DeleteBeanAction(); this.loadUsingTemplate(bean,action); return action.getCount(); } Connection c = null; PreparedStatement ps = null; StringBuilder sql = new StringBuilder("DELETE FROM " + metaData.tablename +" "); StringBuilder sqlWhere = new StringBuilder(""); try { fillWhere(sqlWhere, bean, SEARCH_EXACT); sql.append(" WHERE ").append(sqlWhere); c = this.getConnection(); if(debug){ log("deleteUsingTemplate: " + sql.toString()); } ps = c.prepareStatement(sql.toString(), ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); this.fillPreparedStatement(ps, bean, SEARCH_EXACT, false); return ps.executeUpdate(); } catch(SQLException e) { throw new RuntimeDaoException(new DataAccessException(e)); } finally { this.getManager().close(ps); this.freeConnection(c); } } //11 @Override public int deleteByWhere(String where) { if( !getListenerContainer().isEmpty()){ final DeleteBeanAction action = new DeleteBeanAction(); this.loadByWhere(where,action); return action.getCount(); } Connection c = null; PreparedStatement ps = null; try { c = this.getConnection(); StringBuilder sql = new StringBuilder("DELETE FROM " + metaData.tablename + " " + where); if(debug){ log("deleteByWhere: " + sql); } ps = c.prepareStatement(sql.toString()); return ps.executeUpdate(); } catch(SQLException e) { throw new RuntimeDaoException(new DataAccessException(e)); } finally { this.getManager().close(ps); this.freeConnection(c); } } private void setPreparedStatement(PreparedStatement ps,int pos,Object value,int columnId) throws SQLException { Manager.fillPreparedStatement(ps, pos, value, metaData.sqlTypes[columnId]); } private void setPreparedStatement(PreparedStatement ps,int pos,B bean,int columnId) throws SQLException { setPreparedStatement(ps,pos,bean.getValue(columnId),columnId); } //3.9 SYNC SAVE /** * Save the B bean and referenced beans and imported beans into the database. * * @param bean the B bean to be saved * @param referenceBeans referenced beans beans
* @param importedBeans imported beans
* @return the inserted or updated B bean * @throws RuntimeDaoException */ private B save(B bean,Map referenceBeans,Map> importedBeans) throws RuntimeDaoException{ if(null == bean){ return null; } referenceBeans = MoreObjects.firstNonNull(referenceBeans, Collections.emptyMap()); importedBeans = MoreObjects.firstNonNull(importedBeans, Collections.>emptyMap()); for(Entry entry:referenceBeans.entrySet()){ BaseBean beanToSet = entry.getValue(); if(beanToSet != null){ setReferencedBean(entry.getKey(), bean, beanToSet); } } bean = this.save( bean ); for(Entry> entry:importedBeans.entrySet()){ String ikName = entry.getKey(); Collection importeds = entry.getValue(); if(null != importeds){ setImportedBeans(ikName, bean, importeds); TableManager impManager = managerOf(metaData.getImportedKey(ikName).ownerTable); impManager.save(importeds); } } return bean; } @SuppressWarnings("unchecked") protected B saveFully(B bean,Object[] args){ HashMap referenced = Maps.newHashMap(); int index = 0; for(String key : metaData.foreignKeys.keySet()){ referenced.put(key, (BaseBean) args[index++]); } HashMap> imported = Maps.newHashMap(); for(ForeignKeyMetaData key : metaData.getImportedKeys()){ Object obj = args[index++]; if(obj == null){ imported.put(key.name, null); }else if(obj.getClass().isArray()){ checkArgument(BaseBean.class.isAssignableFrom(obj.getClass().getComponentType()), "INVALID COMPONENT TYPE FOR %s",key.name); imported.put(key.name,Arrays.asList((BaseBean[])obj)); }else if(obj instanceof Collection){ imported.put(key.name,(Collection)obj); }else{ throw new IllegalArgumentException(logString("INVALID TYPE %s FOR %s",obj.getClass(),key.name)); } } return save(bean,referenced,imported); } protected B saveFullyAsTransaction(final B bean, final Object[] args){ return this.runAsTransaction(new Callable(){ @Override public B call() throws Exception { return saveFully(bean , args ); }}); } //_____________________________________________________________________ // // COUNT //_____________________________________________________________________ //25 @Override public int countWhere(String where) { String sql = new StringBuffer("SELECT COUNT(*) AS MCOUNT FROM " + metaData.tablename + " ") .append(null == where ? "" : where).toString(); return runSqlForValue(Long.class, sql).intValue(); } //20 /** * count the number of elements of a specific B bean given the search type * * @param bean the B template to look for * @param searchType exact ? like ? starting like ? * @return the number of rows returned */ @Override public int countUsingTemplate(B bean, int searchType) { Connection c = null; PreparedStatement ps = null; StringBuilder sql = new StringBuilder("SELECT COUNT(*) AS MCOUNT FROM " + metaData.tablename); StringBuilder sqlWhere = new StringBuilder(""); try { if (this.fillWhere(sqlWhere, bean, SEARCH_EXACT) > 0) { sql.append(" WHERE ").append(sqlWhere); } else { if(debug){ log("The bean to look is not initialized... counting all..."); } } c = this.getConnection(); if(debug){ log("countUsingTemplate: " + sql.toString()); } ps = c.prepareStatement(sql.toString(), ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); this.fillPreparedStatement(ps, bean, searchType,false); return getManager().runPreparedStatementForValue(Long.class,ps).intValue(); } catch(SQLException e) { throw new RuntimeDaoException(new DataAccessException(e)); } finally { this.getManager().close(ps); this.freeConnection(c); sql = null; sqlWhere = null; } } /** * fills the given StringBuilder with the sql where clauses constructed using the bean and the search type * @param sqlWhere the StringBuilder that will be filled * @param bean the bean to use for creating the where clauses * @param searchType exact ? like ? starting like ? * @return the number of clauses returned */ private int fillWhere(StringBuilder sqlWhere, B bean, int searchType) { if (bean == null) { return 0; } int dirtyCount = 0; String sqlEqualsOperation = searchType == SEARCH_EXACT ? "=" : " like "; List fields = metaData.columnNames; for(int i=0; i fields = metaData.columnNames; for(int columnId=0; columnId{ private final BaseBean bean; public FieldNullChecker(BaseBean bean) { this.bean = checkNotNull(bean,"bean is null"); } @Override public boolean apply(String input) { return null == bean.getValue(input); } } //_____________________________________________________________________ // // MANY TO MANY: LOAD OTHER BEAN VIA JUNCTION TABLE //_____________________________________________________________________ /** * Retrieves an list of R using the junction table junction table(junctionTablename), given a linked table bean, * specifying the start row and the number of rows. * @param rightType * @param left * @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 * @param * @return a list of R */ public List loadViaJunctionTableAsList( Class rightType,L left, int startRow, int numRows) { checkState(!metaData.getJunctionTablePkMap().isEmpty(),"%s is not junction table",metaData.tablename); checkArgument(null != left,"left is null"); checkArgument(null != rightType,"rightType is null"); RowMetaData leftMetaData = RowMetaData.getMetaData(left.tableName()); RowMetaData rightMetaData = RowMetaData.getMetaData(rightType); checkArgument(!leftMetaData.equals(rightMetaData),"same metadata FOR left AND right type"); checkArgument(metaData.isLinkedTable(leftMetaData.tablename),"INVALID left type %s",left.getClass()); checkArgument(metaData.isLinkedTable(rightMetaData.tablename),"INVALID right type %s",rightType); Map leftFields = metaData.junctionMapOf(leftMetaData.tablename); Map rightFields = metaData.junctionMapOf(rightMetaData.tablename); if(Iterables.tryFind(leftFields.values(), new FieldNullChecker(left)).isPresent()){ return Collections.emptyList(); } Connection c = null; PreparedStatement ps = null; String sql = " SELECT " + rightMetaData.columnFullFields + " FROM "+ metaData.tablename + ", " + rightMetaData.tablename + " WHERE " + Joiner.on(" AND ").join(Iterables.transform(leftFields.keySet(), new Function(){ @Override public String apply(String input) { return input + "=?"; }})) + " AND " + Joiner.on(" AND ").join(Iterables.transform(rightFields.entrySet(), new Function,String>(){ @Override public String apply(Map.Entry input) { return input.getKey() + "=" + input.getValue(); }})); try { c = this.getConnection(); if(debug){ log("loadViaJunctionTableAsList" + sql); } ps = c.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); int pos =1; for(String key:leftFields.keySet()){ String fullName = leftFields.get(key); int columnId = leftMetaData.columnIDOf(fullName); Manager.fillPreparedStatement(ps, pos++, left.getValue(columnId), leftMetaData.sqlTypeOf(columnId)); } BaseTableManager rightManager = baseManagerOf(rightMetaData.tablename); return rightManager.loadByPreparedStatementAsList(ps, null, startRow, numRows); } catch (SQLException e) { throw new RuntimeDaoException(new DaoException(e.getMessage(), e)); } finally { this.getManager().close(ps); this.freeConnection(c); } } private B makeJunctionBean(L left,R right){ Map map = metaData.getJunctionTablePkMap(); checkState(!map.isEmpty(), "%s is not junction table",metaData.tablename); Object[] primaryValues = new Object[metaData.primaryKeyCount]; for(int i=0; i * @param * @param left * @param right */ private void addJunction(L left,R right){ if(hasNullPk(left) || hasNullPk(right)){ return ; } B junction = makeJunctionBean(left,right); if(!existsByPrimaryKey(junction)){ save(junction); } } /** * remove junction between L and R * @param * @param * @param left * @param right */ private int deleteJunction(L left,R right){ if(hasNullPk(left) || hasNullPk(right)){ return 0; } B junction = makeJunctionBean(left,right); return delete(junction); } @SuppressWarnings("unchecked") private void addJunction(L left,R... rights){ if(null != rights){ for(R linked:rights){ addJunction(left,linked); } } } private void addJunction(L left,Collection rights){ if(null != rights){ for(R linked:rights){ addJunction(left,linked); } } } @SuppressWarnings("unchecked") private int deleteJunction(L left,R... rights){ if(null != rights){ return deleteJunction(left, Arrays.asList(rights)); } return 0; } private int deleteJunction(L left,Collection rights){ int count = 0; if(null != rights){ for(R right:rights){ count += deleteJunction(left,right); } } return count; } /** * add junction between B and R if junction not exists * @param * @param junction junction table name * @param bean * @param linked */ protected void addJunction(String junction,B bean,R linked){ baseManagerOf(junction).addJunction(bean,linked); } /** * add junction between B and R if junction not exists * @param * @param junction junction table name * @param bean * @param linkedBeans */ @SuppressWarnings({ "unchecked" }) protected void addJunction(String junction,B bean,R... linkedBeans){ baseManagerOf(junction).addJunction(bean,linkedBeans); } /** * add junction between B and R if junction not exists * @param * @param junction junction table name * @param bean * @param linkedBeans */ @SuppressWarnings({ }) protected void addJunction(String junction,B bean,Collection linkedBeans){ baseManagerOf(junction).addJunction(bean,linkedBeans); } /** * delete junction between B and R * @param * @param junction junction table name * @param bean * @param linked * @return count of deleted rows */ protected int deleteJunction(String junction,B bean,R linked){ return baseManagerOf(junction).deleteJunction(bean,linked); } /** * delete junction between B and R * @param * @param junction junction table name * @param bean * @param linkedBeans * @return count of deleted rows */ @SuppressWarnings({ "unchecked" }) protected int deleteJunction(String junction,B bean,R... linkedBeans){ return baseManagerOf(junction).deleteJunction(bean,linkedBeans); } /** * delete junction between B and R * @param * @param junction junction table name * @param bean * @param linkedBeans * @return count of deleted rows */ protected int deleteJunction(String junction,B bean,Collection linkedBeans){ return baseManagerOf(junction).deleteJunction(bean,linkedBeans); } //28-2 /** decode a resultset and call action * @param rs the resultset to decode * @param fieldList table of the field's associated constants * @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 action interface obj for do something * @return the count dealt by action * @throws IllegalArgumentException */ protected int actionOnResultSet(ResultSet rs, int[] fieldList, int startRow, int numRows, Action action) throws DaoException{ try{ int count = 0; if(0!=numRows){ checkArgument(startRow>=1,"invalid argument:startRow (must >=1)"); checkArgument(null !=action && null != rs,"invalid argument:action OR rs (must not be null)"); rs.absolute(startRow - 1); if (fieldList == null) { if(numRows<0){ for(;rs.next();++count){ action.call(decodeRow(rs)); } }else{ for(;rs.next() && count T getColumnValue(ResultSet resultSet, int columnId) throws SQLException { return (T) Manager.getObject(resultSet,columnId + 1, metaData.columnTypeOf(columnId)); } protected void setColumnValue(B bean,int columnId, Object value ){ if(value instanceof byte[] && ByteBuffer.class.equals(metaData.columnTypeOf(columnId))){ value = ByteBuffer.wrap((byte[])value); } bean.setValue(columnId, value); } //29 /** * Transforms a ResultSet iterating on a B bean. * * @param rs the ResultSet to be transformed * @return bean resulting B bean */ private B decodeRow(ResultSet rs) throws DaoException { B bean = createBean(); try { for(int i=0; i=0 && fieldList[i] loadByPreparedStatementAsList(PreparedStatement ps, int[] fieldList, int startRow, int numRows) throws DaoException { ListAction action = new ListAction(); loadByPreparedStatement(ps,fieldList,startRow,numRows,action); return action.getList(); } //34-2 /** * Loads each element using a prepared statement specifying a list of fields to be retrieved, * and specifying the start row and the number of rows * and dealt by action. * * @param ps the PreparedStatement to be used * @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 fieldList table of the field's associated constants * @param action Action object for do something(not null) * @return the count dealt by action */ private int loadByPreparedStatement(PreparedStatement ps, int[] fieldList, int startRow, int numRows,Action action) throws DaoException { ResultSet rs = null; try { ps.setFetchSize(100); rs = ps.executeQuery(); return this.actionOnResultSet(rs, fieldList, startRow, numRows, action); } catch (DaoException e) { throw e; } catch (SQLException e) { throw new DataAccessException(e); } finally { this.getManager().close(rs); } } //_____________________________________________________________________ // // LISTENER //_____________________________________________________________________ /** foreign key listener for DEELTE RULE */ protected class DeleteRuleListener extends BaseForeignKeyListener{ protected final String fkName; private final ForeignKeyRule deleteRule; private final int[] foreignKeyIds; DeleteRuleListener(String fkName) { checkArgument(metaData.foreignKeys.containsKey(fkName),"INVALID foreign key name %s",fkName); this.fkName = fkName; this.deleteRule = metaData.foreignKeys.get(fkName).deleteRule; this.foreignKeyIds = metaData.foreignKeyIdArrayOf(fkName); } @Override protected List getImportedBeans(F bean) { return getListenerContainer().isEmpty() ? Collections.emptyList() : loadByForeignKeyAsList(fkName,bean,1,-1); } @SuppressWarnings("unchecked") @Override protected void onRemove(List affectedBeans) throws DaoException { switch (deleteRule) { case SET_NULL: for(B bean : affectedBeans){ BaseRow updated = ((BaseRow)bean).clone(); for(int i=0; i container = getListenerContainer(); switch(event){ case UPDATE: container.beforeUpdate(bean); container.afterUpdate(bean); container.done(); break; case DELETE: container.beforeDelete(bean); container.afterDelete(bean); container.done(); break; case INSERT: // DO NOTHING // container.beforeInsert(bean); // container.afterInsert(bean); // container.done(); default: break; } } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("DeleteRuleListener [fkName="); builder.append(fkName); builder.append(", deleteRule="); builder.append(deleteRule); builder.append(", foreignKeyIds="); builder.append(metaData.columnNamesOf(foreignKeyIds)); builder.append("]"); return builder.toString(); } } //35 @Override public void registerListener(TableListener listener) { this.getListenerContainer().add(listener); if(debug){ log("REGISTER TABLE LISTENER {}",listener); } } //36 @Override public void unregisterListener(TableListener listener) { this.getListenerContainer().remove(listener); if(debug){ log("UNREGISTER TABLE LISTENER {}",listener); } } //37-2 /** * bind foreign key listener to foreign table:
* DELETE RULE
*/ public void bindForeignKeyListenerForDeleteRule(){ for(Entry> entry : getForeignKeyDeleteListeners().entrySet()){ TableManager manager = managerOf(metaData.foreignKeys.get(entry.getKey()).foreignTable); manager.registerListener(entry.getValue()); } } //37-3 /** * unbind foreign key listener from all of foreign tables
* @see #bindForeignKeyListenerForDeleteRule() */ public void unbindForeignKeyListenerForDeleteRule(){ for(Entry> entry : getForeignKeyDeleteListeners().entrySet()){ TableManager manager = managerOf(metaData.foreignKeys.get(entry.getKey()).foreignTable); manager.unregisterListener(entry.getValue()); } } //_____________________________________________________________________ // // UTILS //_____________________________________________________________________ //40 /** * Retrieves the manager object used to get connections. * * @return the manager used */ protected Manager getManager() { return Manager.getInstance(); } //41 /** * Frees the connection. * * @param c the connection to release */ protected void freeConnection(Connection c) { // back to pool this.getManager().releaseConnection(c); } //42 /** * Gets the connection. */ protected Connection getConnection() throws DaoException { try { return this.getManager().getConnection(); } catch(SQLException e) { throw new DataAccessException(e); } } private int loadBySqlForAction(String sql, Object[] argList, int[] fieldList,int startRow, int numRows,Action action){ PreparedStatement ps = null; Connection connection = null; try { connection = this.getConnection(); if(debug){ log("loadBySqlForAction:" + sql ); } ps = connection.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); Manager.fillPrepareStatement(ps, argList); return this.loadByPreparedStatement(ps, fieldList, startRow, numRows, action); } catch (SQLException e) { throw new RuntimeDaoException(new DataAccessException(e)); } finally { this.getManager().close(ps); this.freeConnection(connection); } } @Override public List runSqlAsList(String sql, Object... argList) throws RuntimeDaoException{ return getManager().runSqlAsList(sql, argList); } @Override public List> runSqlForMap(Map> targetTypes, String sql,Object... argList) throws RuntimeDaoException{ return getManager().runSqlForMap(targetTypes, sql, argList); } @Override public List runSqlAsList(Class targetType, String sql,Object... argList) throws RuntimeDaoException{ return getManager().runSqlAsList(targetType, sql, argList); } @Override public T runSqlForValue(Class targetType,String sql, Object... argList) throws RuntimeDaoException{ return getManager().runSqlForValue(targetType, sql, argList); } @Override public T runAsTransaction(Callable fun) { return getManager().runAsTransaction(fun); } @Override public void runAsTransaction(Runnable fun) { getManager().runAsTransaction(fun); } //45 /** * @param PK type * @param type * @param beans * @return return a primary key list from B array * @see #toPrimaryKeyList(Class,Collection) */ @SuppressWarnings("unchecked") protected List toPrimaryKeyList(Classtype,B... beans){ if(null == beans || beans.length ==0){ return Collections.emptyList(); } return toPrimaryKeyList(type,Arrays.asList(beans)); } private class ColumnTransformer implements Function{ private int columnId; public ColumnTransformer(int columnId) { checkArgument(columnId >=0 && columnId < metaData.columnCount,"INVALID column id %s",columnId); this.columnId = columnId; } @Override public T apply(B input) { return input == null ? null : input.getValue(columnId); } } /** * listener event:
* {@code INSERT} insert a bean
* {@code UPDATE} update a bean
* {@code DELETE} delete a bean
* @author guyadong * */ public static enum Event{ /** insert a bean */INSERT, /** update a bean */UPDATE, /** delete a bean */DELETE } //46 /** * return a primary key list from B collection
* throw {@link UnsupportedOperationException} if there is more than a primary key * @param PK type * @param type PK type * @param beans input beans */ protected List toPrimaryKeyList(Classtype,Collection beans){ if(metaData.primaryKeyNames.length != 1){ throw new UnsupportedOperationException(); } if(null == beans){ return Collections.emptyList(); } checkArgument(metaData.columnTypeOf(metaData.primaryKeyIds[0]).equals(type),"INVALID primary key type: " + type); return ImmutableList.copyOf(Iterables.transform(beans, new ColumnTransformer(metaData.primaryKeyIds[0]))); } private Object[] toPkValues(B bean,int[] selfFkIds){ checkArgument(selfFkIds.length == metaData.primaryKeyNames.length,"MISMATCH SIZE of primary keys"); return bean.asValueArray(selfFkIds); } /** * return bean list ( include bean specified by {@code primaryKeys} ) by the self-reference field specified by fkName
* first element is top bean * @param fkName foreign key name * @param primaryKeys values of primary keys * @return empty list if input primary key is {@code null}
* first element equal last if self-reference field is cycle * @throws RuntimeDaoException */ protected List listOfSelfRef(String fkName,Object... primaryKeys) throws RuntimeDaoException{ List list = new ArrayList<>(); int[] selfFks = metaData.foreignKeyIdArrayOf(fkName); for(B ref = loadByPrimaryKey(primaryKeys) ; null != ref ; ref = loadByPrimaryKey(toPkValues(ref,selfFks))){ list.add(ref); Object[] refPk = ref.primaryValues(); Object[] refSelf = toPkValues(ref,selfFks); if(Arrays.equals(refPk,refSelf) || (list.size() > 1 && Arrays.equals(refPk,primaryKeys))){ // cycle reference break; } } Collections.reverse(list); return list; } /** * return bean list ( include {@code bean} ) by the self-reference field specified by fkName
* first element is top bean * @param fkName foreign key name * @param bean * @return empty list if input primary key is {@code null}
* first element equal last if self-reference field is cycle * @throws RuntimeDaoException */ protected List listOfSelfRef(String fkName,B bean) throws RuntimeDaoException{ return bean == null ? Collections.emptyList() : listOfSelfRef(fkName,bean.primaryValues()); } /** * get level count on the self-reference field specified by fkName * @param fkName foreign key name * @param primaryKeys values of primary keys * @return 0 if input primary key is {@code null}
* -1 if self-reference field is cycle * @throws RuntimeDaoException */ protected int levelOfSelfRef(String fkName,Object... primaryKeys) throws RuntimeDaoException{ int count = 0 ; int[] selfFks = metaData.foreignKeyIdArrayOf(fkName); for(B ref = loadByPrimaryKey(primaryKeys) ; null != ref ; ++count,ref = loadByPrimaryKey(toPkValues(ref,selfFks))){ Object[] refPk = ref.primaryValues(); Object[] refSelf = toPkValues(ref,selfFks); if( (Arrays.equals(refPk,refSelf)) || (count > 0 && Arrays.equals(refPk,primaryKeys))){ // cycle reference return -1; } } return count; } /** * get level count on the self-reference field specified by fkName * @param fkName foreign key name * @param bean * @return 0 if input primary key is {@code null}
* -1 if self-reference field is cycle * @throws RuntimeDaoException */ protected int levelOfSelfRef(String fkName,B bean) throws RuntimeDaoException{ return bean == null ? 0 : levelOfSelfRef(fkName,bean.primaryValues()); } /** * test whether the self-reference field specified by fkName is cycle * @param fkName foreign key name * @param primaryKeys values of primary keys * @return true if the self-reference field is cycle * @throws RuntimeDaoException */ protected boolean isCycleOfSelfRef(String fkName,Object... primaryKeys) throws RuntimeDaoException{ return levelOfSelfRef(fkName,primaryKeys) < 0; } /** * test whether the self-reference field specified by fkName is cycle * @param fkName foreign key name * @param bean * @return true if the self-reference field is cycle * @throws RuntimeDaoException */ protected boolean isCycleOfSelfRef(String fkName,B bean) throws RuntimeDaoException{ return bean == null ? false : levelOfSelfRef(fkName,bean.primaryValues()) < 0; } /** * Ensures the self-reference field specified by fkName is not cycle * @param fkName foreign key name * @param primaryKey * @return always {@code primaryKey} * @throws IllegalStateException if self-reference field is cycle * @throws RuntimeDaoException * @see #isCycleOfSelfRef(String,Object[]) */ protected T checkCycleOfSelfRef(String fkName,T primaryKey) throws IllegalStateException,RuntimeDaoException{ if(isCycleOfSelfRef(fkName,primaryKey)){ throw new IllegalStateException("cycle on foreign key: " + fkName); } return primaryKey; } /** * Ensures the self-reference field specified by fkName is not cycle * @param fkName foreign key name * @param bean * @return always {@code bean} * @throws IllegalStateException if self-reference field is cycle * @throws RuntimeDaoException * @see {@link #checkCycleOfSelfRef(String, Object)} */ protected B checkCycleOfSelfRef(String fkName,B bean) throws IllegalStateException,RuntimeDaoException{ if(isCycleOfSelfRef(fkName,bean)){ throw new IllegalStateException("cycle on foreign key: " + fkName); } return bean; } /** * return B bean that with {@code null} self-reference field specified by fkName * @param fkName foreign key name * @param primaryKeys values of primary keys * @return B instance * @throws IllegalArgumentException if input primary keys has {@code null} * @throws IllegalStateException if self-reference field is cycle * @throws ObjectRetrievalException not found record by primary key * @throws RuntimeDaoException */ protected B topOfSelfRef(String fkName,Object... primaryKeys) throws IllegalArgumentException,IllegalStateException,ObjectRetrievalException,RuntimeDaoException{ checkArgument(!hasNull(primaryKeys),"primaryKeys has null element"); int[] selfFks = metaData.foreignKeyIdArrayOf(fkName); B ref = loadByPrimaryKeyChecked(primaryKeys); int count = 0 ; Object[] refSelf = toPkValues(ref,selfFks); for(;null != ref && !hasNull(refSelf);){ Object[] refPk = ref.primaryValues(); refSelf = toPkValues(ref,selfFks); if( (Arrays.equals(refPk,refSelf)) || (++count > 1 && Arrays.equals(refPk,primaryKeys))){ // cycle reference throw new IllegalStateException("cycle on field: " + "parent"); } checkState(Arrays.equals(refPk,refSelf) || (++count > 1 && Arrays.equals(refPk,primaryKeys))); ref = loadByPrimaryKeyChecked(refSelf); } return ref; } /** * return B bean that with {@code null} self-reference field specified by fkName * @param fkName foreign key name * @param bean * @return B instance * @throws IllegalArgumentException if input primary keys has {@code null} * @throws IllegalStateException if self-reference field is cycle * @throws ObjectRetrievalException not found record by primary key * @throws RuntimeDaoException */ protected B topOfSelfRef(String fkName,B bean) throws IllegalArgumentException,IllegalStateException,ObjectRetrievalException,RuntimeDaoException{ return topOfSelfRef(fkName,checkNotNull(bean,"bean is null").primaryValues()); } private LinkedHashSet doListOfChild(String fkName, B bean,LinkedHashSet set){ if(existsByPrimaryKey(bean)){ checkState(!set.contains(bean),"cycle on foreign key: " + fkName); set.add(bean); List childs = loadByForeignKeyAsList(fkName, bean, 1, -1); for(B c:childs){ doListOfChild(fkName,c,set); } } return set; } /** * return child bean list (self included) by the self-reference field specified by fkName
* throw {@link RuntimeDaoException} if self-reference field is cycle * @param fkName foreign key name * @param bean * @return child bean list,empty list if not found record * @throws IllegalStateException if self-reference field is cycle * @throws RuntimeDaoException */ protected List childListOfSelfRef(String fkName, B bean) throws IllegalStateException,RuntimeDaoException{ return new ArrayList(doListOfChild(fkName,bean,new LinkedHashSet())); } /** * return child bean list (self included) by the self-reference field specified by fkName
* throw {@link RuntimeDaoException} if self-reference field is cycle * @param fkName foreign key name * @param primaryKeys values of primary keys * @return child bean list,empty list if not found record * @throws IllegalStateException if self-reference field is cycle * @throws RuntimeDaoException */ protected List childListOfSelfRef(String fkName, Object... primaryKeys) throws IllegalStateException,RuntimeDaoException{ return childListOfSelfRef(fkName,createBean(primaryKeys)); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((metaData == null) ? 0 : metaData.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } BaseTableManager other = (BaseTableManager) obj; if (metaData == null) { if (other.metaData != null) { return false; } } else if (!metaData.equals(other.metaData)) { return false; } return true; } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append(getClass().getSimpleName() + " [metaData="); builder.append(metaData); builder.append("]"); return builder.toString(); } /** * set debug flag that determine if output log message,default : false * @param debug flag for debug message output */ static void setDebug(boolean debug) { BaseTableManager.debug = debug; } /** * @return debug flag */ static boolean isDebug() { return debug; } class DeleteBeanAction implements Action{ private final AtomicInteger count=new AtomicInteger(0); public DeleteBeanAction() { } @Override public void call(B bean){ delete(bean); count.incrementAndGet(); } public int getCount(){ return count.get(); } } }