Package siena.jdbc

Source Code of siena.jdbc.H2PersistenceManager

package siena.jdbc;

import java.lang.reflect.Field;
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.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

import siena.ClassInfo;
import siena.Generator;
import siena.Id;
import siena.Query;
import siena.QueryFilterSearch;
import siena.SienaException;
import siena.Util;
import siena.jdbc.h2.FullText;

public class H2PersistenceManager extends JdbcPersistenceManager {
  private static final String DB = "H2";
 
  private String dbMode = "h2";
 
  protected static Map<String, Boolean> tableIndexMap = new ConcurrentHashMap<String, Boolean>();

  public H2PersistenceManager() {
   
  }
 
  public H2PersistenceManager(ConnectionManager connectionManager, Class<?> listener) {
    super(connectionManager, listener);
  }
 
  public H2PersistenceManager(ConnectionManager connectionManager, Class<?> listener, String dbMode) {
    super(connectionManager, listener);
    this.dbMode = dbMode;
  }
 
  @Override
  public void closeConnection() {
    // In H2, if we close the connection, the DB is closed even when using DB_CLOSE_DELAY=-1
    // H2 Bug???
    //super.closeConnection();
  }

  /*
   * Overrides the batch insert since H2 getGeneratedKeys doesn't return all generated identities but only the last one.
   * This is a known limitation: http://markmail.org/message/hsgzgktbj4srz657
   * It is planned in H2 v1.4 roadmap: http://www.h2database.com/html/roadmap.html
   * Meanwhile, no batch insert is possible
   * 
   * (non-Javadoc)
   * @see siena.jdbc.JdbcPersistenceManager#insertBatchWithAutoIncrementKey(siena.jdbc.JdbcPersistenceManager.JdbcClassInfo, java.util.Map)
   */
  @Override
  protected int insertBatchWithAutoIncrementKey(JdbcClassInfo classInfo, Map<JdbcClassInfo, List<Object>> objMap) throws SQLException, IllegalAccessException {
    PreparedStatement ps = null;
    ps = getConnection().prepareStatement(classInfo.insertSQL,
        Statement.RETURN_GENERATED_KEYS);
   
    int res = 0;
    for(Object obj: objMap.get(classInfo)){
      for (Field field : classInfo.keys) {
        Id id = field.getAnnotation(Id.class);
        if (id.value() == Generator.UUID) {
          field.set(obj, UUID.randomUUID().toString());
        }
      }
      // TODO: implement primary key generation: SEQUENCE
      addParameters(obj, classInfo.insertFields, ps, 1);
      ps.executeUpdate();
     
      if(!classInfo.generatedKeys.isEmpty()){
        ResultSet gk = ps.getGeneratedKeys();
        int i;
        while(gk.next()) {
          i=1;
          for (Field field : classInfo.generatedKeys) {
            field.setAccessible(true);
            JdbcMappingUtils.setFromObject(obj, field, gk.getObject(i++));
          }
        }
      }
     
      res++;
    }
   
    return res;
  }

  @Override
  public void save(Object obj) {   
    JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(obj.getClass());

    List<String> keyNames = new ArrayList<String>();
    for (Field field : classInfo.keys) {
      keyNames.add(field.getName());
    }
   
    PreparedStatement ps = null;
    try {
      Field idField = classInfo.info.getIdField();
      Object idVal = Util.readField(obj, idField);

      if (idVal == null) {
        insert(obj);
      } else {
        // in H2 "on duplicate" is not supported but MERGE is
        // merge into employees (id, first_name, last_name) values(1, 'test2', 'test2');
        List<String> allColumns = new ArrayList<String>();
        JdbcClassInfo.calculateColumns(classInfo.allFields, allColumns, null, "");
        String[] is = new String[allColumns.size()];
        Arrays.fill(is, "?");
       
        ps = getConnection().prepareStatement(
            "MERGE INTO "+ classInfo.tableName + " (" + Util.join(allColumns, ",") + ") "
            + "VALUES(" + Util.join(Arrays.asList(is), ",") + ")" 
        );
       
        int i = 1;
        i = addParameters(obj, classInfo.allFields, ps, i);
        ps.executeUpdate();       
      }
    } catch (SienaException e) {
      throw e;
    } catch (Exception e) {
      throw new SienaException(e);
    } finally {
      JdbcDBUtils.closeStatementAndConnection(this, ps);
    }
  }
 


  @Override
  public int save(Object... objects) {
    return save(Arrays.asList(objects));
  }

  @Override
  public int save(Iterable<?> objects) {
    Map<JdbcClassInfo, List<Object>> generatedObjMap = new HashMap<JdbcClassInfo, List<Object>>();
    Map<JdbcClassInfo, List<Object>> objMap = new HashMap<JdbcClassInfo, List<Object>>();
    PreparedStatement ps = null;
   
    for(Object obj:objects){
      JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(obj.getClass());
      Field idField = classInfo.info.getIdField();
      Object idVal = Util.readField(obj, idField);
     
      if(idVal == null && !classInfo.generatedKeys.isEmpty()){
        if(!generatedObjMap.containsKey(classInfo)){
          List<Object> l = new ArrayList<Object>();
          l.add(obj);
          generatedObjMap.put(classInfo, l);
        }else{
          generatedObjMap.get(classInfo).add(obj);
        }
      } else {
        if(!objMap.containsKey(classInfo)){
          List<Object> l = new ArrayList<Object>();
          l.add(obj);
          objMap.put(classInfo, l);
        }else{
          objMap.get(classInfo).add(obj);
        }
      }
    }
   
    int total = 0;
    try {
      // these are the insertions with generated keys
      for(JdbcClassInfo classInfo: generatedObjMap.keySet()){
        total += insert(generatedObjMap.get(classInfo));
      }
     
      // these are the insertions or updates without generated keys
      // can't use batch in Postgres with generated keys... known bug
      // http://postgresql.1045698.n5.nabble.com/PreparedStatement-batch-statement-impossible-td3406927.html
      for(JdbcClassInfo classInfo: objMap.keySet()){
        List<String> keyNames = new ArrayList<String>();
        for (Field field : classInfo.keys) {
          keyNames.add(field.getName());
        }
       
        // in H2 "on duplicate" is not supported but MERGE is
        // merge into employees (id, first_name, last_name) values(1, 'test2', 'test2');
        List<String> allColumns = new ArrayList<String>();
        JdbcClassInfo.calculateColumns(classInfo.allFields, allColumns, null, "");
        String[] is = new String[allColumns.size()];
        Arrays.fill(is, "?");
     
        ps = getConnection().prepareStatement(
            "MERGE INTO "+ classInfo.tableName + " (" + Util.join(allColumns, ",") + ") "
            + "VALUES(" + Util.join(Arrays.asList(is), ",") + ")" 
        );
       
        for(Object obj: objMap.get(classInfo)){       
          int i = 1;
          i = addParameters(obj, classInfo.allFields, ps, i);
          ps.addBatch();
        }
       
        int[] res = ps.executeBatch();
       
        total+=res.length;
      }
     
      return total;     
    } catch (SienaException e) {
      throw e;
    } catch (Exception e) {
      throw new SienaException(e);
    } finally {
      JdbcDBUtils.closeStatementAndConnection(this, ps);
    }
  }
   
  @Override
  public void init(Properties p) {
    super.init(p);
    // initializes the search mechanism in H2
    try {
      FullText.init(this.getConnection());
    } catch (SQLException e) {
      throw new SienaException(e);
    }
  }
 
  protected <T> List<T> doSearch(Query<T> query, int limit, int offset){
    // TODO this is a very raw impl: need some work certainly
    try {
      Connection conn = this.getConnection();
      ClassInfo ci = ClassInfo.getClassInfo(query.getQueriedClass());
      // doesn't index a table that has already been indexed
      if(!tableIndexMap.containsKey(ci.tableName)){
        List<String> colList = ci.getUpdateFieldsColumnNames();
        String cols = null;
        if(!colList.isEmpty()){
          cols = "";
          // removes auto generated IDs from index
          int sz = colList.size();
          for (int i=0; i<sz; i++) {
            if("h2".equals(dbMode)) cols+=colList.get(i).toUpperCase();
            // !!! mysql mode means case INsensitive to lowercase !!!!
            else if("mysql".equals(dbMode)) cols+=colList.get(i).toLowerCase();
            else cols+=colList.get(i).toUpperCase();
           
            if(i<sz-1) cols += ",";
          }
        }
        // creates the index
        FullText.createIndex(conn, "PUBLIC", ci.tableName.toUpperCase(), cols);
        tableIndexMap.put(ci.tableName, true);
      }
     
      String searchString = "";
      Iterator<QueryFilterSearch> it = query.getSearches().iterator();
      boolean first = true;
      while(it.hasNext()){
        if(!first){
          searchString += " ";
        }else {
          first = false;
        }
        searchString += it.next().match;       
      }
     
      ResultSet rs = FullText.searchData(conn, searchString, limit, offset);
      List<T> res = new ArrayList<T>();
      Field idField = ci.getIdField();
      Class<?> idClass = idField.getType();
      while(rs.next()) {
        //String queryStr = rs.getString("QUERY");
        //String score = rs.getString("SCORE");
        //Array columns = rs.getArray("COLUMNS");
        Object[] keys = (Object[])rs.getArray("KEYS").getArray();
        // convert keys into real type if the key is not a string
        Object[] realKeys = null;
        if(idField.getType() != String.class){
          realKeys = new Object[keys.length];
          for(int i=0; i< keys.length; i++){
            realKeys[i] = Util.fromString(idClass, (String)keys[i]);
          }
        }else {
          realKeys = keys;
        }
        if(res == null) res = this.getByKeys(query.getQueriedClass(), realKeys);
        else res.addAll(this.getByKeys(query.getQueriedClass(), realKeys));
      }
      return res;
    } catch (SQLException e) {
      throw new SienaException(e);
    }
  }
 
  protected <T> List<T> doSearchKeys(Query<T> query, int limit, int offset){
    // TODO this is a very raw impl: need some work certainly
    try {
      Connection conn = this.getConnection();
      ClassInfo ci = ClassInfo.getClassInfo(query.getQueriedClass());
      // doesn't index a table that has already been indexed
      if(!tableIndexMap.containsKey(ci.tableName)){
        List<String> colList = ci.getUpdateFieldsColumnNames();
        String cols = null;
        if(!colList.isEmpty()){
          cols = "";
          // removes auto generated IDs from index
          int sz = colList.size();
          for (int i=0; i<sz; i++) {
            if("h2".equals(dbMode)) cols+=colList.get(i).toUpperCase();
            // !!! mysql mode means case INsensitive to lowercase !!!!
            else if("mysql".equals(dbMode)) cols+=colList.get(i).toLowerCase();
            else cols+=colList.get(i).toUpperCase();
           
            if(i<sz-1) cols += ",";
          }
        }
        // creates the index
        FullText.createIndex(conn, "PUBLIC", ci.tableName.toUpperCase(), cols);
        tableIndexMap.put(ci.tableName, true);
      }
     
      String searchString = "";
      Iterator<QueryFilterSearch> it = query.getSearches().iterator();
      boolean first = true;
      while(it.hasNext()){
        if(!first){
          searchString += " ";
        }else {
          first = false;
        }
        searchString += it.next().match;       
      }
     
      ResultSet rs = FullText.searchData(conn, searchString, limit, offset);
      List<T> res = new ArrayList<T>();
      Class<T> clazz = query.getQueriedClass();
      while(rs.next()) {
        //String queryStr = rs.getString("QUERY");
        //String score = rs.getString("SCORE");
        //Array columns = rs.getArray("COLUMNS");
        Object[] keys = (Object[])rs.getArray("KEYS").getArray();
        for(Object key: keys){
          T obj = Util.createObjectInstance(clazz);
          for (Field field : JdbcClassInfo.getClassInfo(clazz).keys) {
            JdbcMappingUtils.setFromObject(obj, field, key);
          }
         
          res.add(obj);
        }       
      }
      return res;
    } catch (SQLException e) {
      throw new SienaException(e);
    } catch (Exception e) {
      throw new SienaException(e);
    }
  }
 
  protected <T> int doSearchCount(Query<T> query){
    // TODO this is a very raw impl: need some work certainly
    try {
      Connection conn = this.getConnection();
      ClassInfo ci = ClassInfo.getClassInfo(query.getQueriedClass());
      // doesn't index a table that has already been indexed
      if(!tableIndexMap.containsKey(ci.tableName)){
        List<String> colList = ci.getUpdateFieldsColumnNames();
        String cols = null;
        if(!colList.isEmpty()){
          cols = "";
          // removes auto generated IDs from index
          int sz = colList.size();
          for (int i=0; i<sz; i++) {
            if("h2".equals(dbMode)) cols+=colList.get(i).toUpperCase();
            // !!! mysql mode means case INsensitive to lowercase !!!!
            else if("mysql".equals(dbMode)) cols+=colList.get(i).toLowerCase();
            else cols+=colList.get(i).toUpperCase();
           
            if(i<sz-1) cols += ",";
          }
        }
        // creates the index
        FullText.createIndex(conn, "PUBLIC", ci.tableName.toUpperCase(), cols);
        tableIndexMap.put(ci.tableName, true);
      }
     
      String searchString = "";
      Iterator<QueryFilterSearch> it = query.getSearches().iterator();
      boolean first = true;
      while(it.hasNext()){
        if(!first){
          searchString += " ";
        }else {
          first = false;
        }
        searchString += it.next().match;       
      }
     
      ResultSet rs = FullText.searchData(conn, searchString, 0, 0);
      int count = 0;
      while(rs.next()) {
        //String queryStr = rs.getString("QUERY");
        //String score = rs.getString("SCORE");
        //Array columns = rs.getArray("COLUMNS");
        Object[] keys = (Object[])rs.getArray("KEYS").getArray();
        count += keys.length;
      }
      return count;
    } catch (SQLException e) {
      throw new SienaException(e);
    }
  }
 
  @Override
  public <T> List<T> fetch(Query<T> query) {
    if(query.getSearches().isEmpty()){
      return super.fetch(query);
    }
    else {
      return doSearch(query, 0, 0);
    }
  }

  @Override
  public <T> List<T> fetch(Query<T> query, int limit) {
    if(query.getSearches().isEmpty()){
      return super.fetch(query, limit);
    }
    else {
      return doSearch(query, limit, 0);
    }
  }

  @Override
  public <T> List<T> fetch(Query<T> query, int limit, Object offset) {
    if(query.getSearches().isEmpty()){
      return super.fetch(query, limit, (Integer)offset);
    }
    else {
      return doSearch(query, limit, (Integer)offset);
    }
  }

  @Override
  public <T> List<T> fetchKeys(Query<T> query) {
    if(query.getSearches().isEmpty()){
      return super.fetchKeys(query);
    }
    else {
      return doSearchKeys(query, 0, 0);
    }
  }

  @Override
  public <T> List<T> fetchKeys(Query<T> query, int limit) {
    if(query.getSearches().isEmpty()){
      return super.fetchKeys(query, limit);
    }
    else {
      return doSearchKeys(query, limit, 0);
    }
  }

  @Override
  public <T> List<T> fetchKeys(Query<T> query, int limit, Object offset) {
    if(query.getSearches().isEmpty()){
      return super.fetchKeys(query, limit, (Integer)offset);
    }
    else {
      return doSearchKeys(query, limit, (Integer)offset);
    }
  }

  @Override
  public <T> Iterable<T> iter(Query<T> query) {
    // TODO Auto-generated method stub
    return super.iter(query);
  }

  @Override
  public <T> Iterable<T> iter(Query<T> query, int limit) {
    // TODO Auto-generated method stub
    return super.iter(query, limit);
  }

  @Override
  public <T> Iterable<T> iter(Query<T> query, int limit, Object offset) {
    // TODO Auto-generated method stub
    return super.iter(query, limit, offset);
  }

  @Override
  public <T> int count(Query<T> query) {
    if(query.getSearches().isEmpty()){
      return super.count(query);
    }
    else {
      return doSearchCount(query);
    }
  }
 
 
}
TOP

Related Classes of siena.jdbc.H2PersistenceManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.