/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.xream.x7.repository.dao; import io.xream.x7.common.bean.*; import io.xream.x7.common.bean.condition.InCondition; import io.xream.x7.common.bean.condition.RefreshCondition; import io.xream.x7.common.repository.X; import io.xream.x7.common.util.BeanMapUtil; import io.xream.x7.common.util.LoggerProxy; import io.xream.x7.common.util.StringUtil; import io.xream.x7.common.web.Page; import io.xream.x7.repository.CriteriaToSql; import io.xream.x7.repository.KeyOne; import io.xream.x7.repository.SqlParsed; import io.xream.x7.repository.exception.TooManyResultsException; import io.xream.x7.repository.mapper.DataObjectConverter; import io.xream.x7.repository.mapper.Dialect; import io.xream.x7.repository.mapper.Mapper; import io.xream.x7.repository.mapper.MapperFactory; import io.xream.x7.repository.util.ResultSortUtil; import io.xream.x7.repository.util.SqlParserUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.ColumnMapRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.SingleColumnRowMapper; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.*; /** * @author Sim */ public class DaoImpl implements Dao { @Autowired private CriteriaToSql criteriaToSql; @Autowired private Dialect dialect; @Autowired private JdbcTemplate jdbcTemplate; private Logger logger = LoggerFactory.getLogger(DaoImpl.class); @Override public boolean createBatch(List<? extends Object> objList) { if (objList.isEmpty()) return false; Object obj = objList.get(0); Class clz = obj.getClass(); String sql = MapperFactory.getSql(clz, Mapper.CREATE); LoggerProxy.debug(clz, sql); Parsed parsed = Parser.get(clz); final int batchSize = 500; try { this.jdbcTemplate.batchUpdate(sql, objList, batchSize, (pstmt, o) -> { List<Object> valueList = DataObjectConverter.objectToListForCreate(o, parsed.getBeanElementList(), dialect); int i = 1; for (Object value : valueList) { pstmt.setObject(i++, value); } }); } catch (Exception e) { throw DaoExceptionTranslator.onRollback(obj, e, logger); } return true; } @Override public <T> boolean remove(KeyOne<T> keyOne) { Class clz = keyOne.getClzz(); String sql = MapperFactory.getSql(clz, Mapper.REMOVE); LoggerProxy.debug(clz, keyOne.get()); LoggerProxy.debug(clz, sql); return this.jdbcTemplate.update(sql, keyOne.get()) > 0; } @Override public long create(Object obj) { Class clz = obj.getClass(); try { String sql = MapperFactory.getSql(clz, Mapper.CREATE); Parsed parsed = Parser.get(clz); Long keyOneValue = parsed.tryToGetLongKey(obj); boolean isAutoIncreaseId = parsed.isAutoIncreaseId(keyOneValue); List<Object> valueList = DataObjectConverter.objectToListForCreate(obj, parsed.getBeanElementList(), dialect); LoggerProxy.debug(clz, valueList); LoggerProxy.debug(clz, sql); KeyHolder keyHolder = new GeneratedKeyHolder(); if (isAutoIncreaseId) { this.jdbcTemplate.update(connection -> { PreparedStatement pstmt = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); int i = 1; for (Object value : valueList) { pstmt.setObject(i++, value); } return pstmt; }, keyHolder); } else { this.jdbcTemplate.update(connection -> { PreparedStatement pstmt = connection.prepareStatement(sql); int i = 1; for (Object value : valueList) { pstmt.setObject(i++, value); } return pstmt; }); } if (isAutoIncreaseId) { return keyHolder.getKey().longValue(); } else { return keyOneValue; } } catch (Exception e) { throw DaoExceptionTranslator.onRollback(obj, e, logger); } } @Override public boolean createOrReplace(Object obj) { Class clz = obj.getClass(); try { String createSql = MapperFactory.getSql(clz, Mapper.CREATE); final String sql = this.dialect.createOrReplaceSql(createSql); Parsed parsed = Parser.get(clz); List<Object> valueList = DataObjectConverter.objectToListForCreate(obj, parsed.getBeanElementList(), dialect); LoggerProxy.debug(clz, valueList); LoggerProxy.debug(clz, sql); this.jdbcTemplate.update(connection -> { PreparedStatement pstmt = connection.prepareStatement(sql); int i = 1; for (Object value : valueList) { pstmt.setObject(i++, value); } return pstmt; }); return true; } catch (Exception e) { throw DaoExceptionTranslator.onRollback(obj, e, logger); } } @Override public List<Map<String, Object>> list(Class clz, String sql, List<Object> conditionSet) { sql = SqlUtil.filter(sql); Parsed parsed = Parser.get(clz); sql = SqlParserUtil.mapperForManu(sql, parsed); LoggerProxy.debug(clz, sql); return queryForList(sql, clz, conditionSet, this.dialect, jdbcTemplate); } @Override public <T> T get(KeyOne<T> keyOne) { Class clz = keyOne.getClzz(); String sql = MapperFactory.getSql(clz, Mapper.GET_ONE); LoggerProxy.debug(clz, sql); List<T> list = this.queryForList(sql, keyOne.getClzz(), Arrays.asList(keyOne.get()), this.dialect, this.jdbcTemplate); if (list.isEmpty()) return null; return list.get(0); } @Override public <T> List<T> list(Object conditionObj) { Class clz = conditionObj.getClass(); String sql = MapperFactory.getSql(clz, Mapper.LOAD); Parsed parsed = Parser.get(clz); Map<String, Object> queryMap = DataObjectConverter.objectToMapForQuery(parsed, conditionObj); sql = SqlUtil.concat(parsed, sql, queryMap); LoggerProxy.debug(clz, sql); return queryForList(sql, clz, queryMap.values(), this.dialect, jdbcTemplate); } @Override public <T> List<T> list(Criteria criteria) { Class clz = criteria.getClz(); SqlParsed sqlParsed = SqlUtil.fromCriteria(criteria, criteriaToSql, dialect); String sql = sqlParsed.getSql().toString(); LoggerProxy.debug(clz, sql); List<Object> valueList = criteria.getValueList(); List<T> list = queryForList(sql, clz, valueList, this.dialect, jdbcTemplate); ResultSortUtil.sort(list, criteria, Parser.get(clz)); return list; } @Override public <T> Page<T> find(Criteria criteria) { Class clz = criteria.getClz(); SqlParsed sqlParsed = SqlUtil.fromCriteria(criteria, criteriaToSql, dialect); String sql = sqlParsed.getSql().toString(); LoggerProxy.debug(clz, sql); List<Object> valueList = criteria.getValueList(); List<T> list = queryForList(sql, clz, valueList, this.dialect, jdbcTemplate); Parsed parsed = Parser.get(clz); ResultSortUtil.sort(list, criteria, parsed); Page<T> pagination = PageBuilder.build(criteria, list, () -> getCount(clz, sqlParsed.getCountSql(), valueList)); return pagination; } /** * getCount * * @param sql * @param list * @return */ private long getCount(Class clz, String sql, Collection<Object> list) { LoggerProxy.debug(clz, sql); Object obj = this.queryForMapList(sql, list, dialect, jdbcTemplate).get(0).get("count"); return Long.valueOf(obj.toString()); } /** * 没有特殊需求,请不要调用此代码 * * @param obj * @param sql */ @Deprecated @Override public boolean execute(Object obj, String sql) { Class clz = obj.getClass(); Parsed parsed = Parser.get(obj.getClass()); sql = SqlUtil.filter(sql); sql = SqlParserUtil.mapperForManu(sql, parsed); LoggerProxy.debug(clz, sql); this.jdbcTemplate.execute(sql); return true; } @Override public boolean refreshByCondition(RefreshCondition refreshCondition) { Class clz = refreshCondition.getClz(); Parsed parsed = Parser.get(clz); String sql = SqlUtil.buildRefresh(parsed, refreshCondition, this.criteriaToSql); List<Object> valueList = refreshCondition.getValueList(); LoggerProxy.debug(clz, valueList); LoggerProxy.debug(clz, sql); return update(sql, valueList, dialect, jdbcTemplate); } @Override public <T> boolean refresh(T t) { Class clz = t.getClass(); Object[] arr = SqlUtil.refresh(t,clz); String sql = (String)arr[0]; Collection<Object> valueList = (Collection<Object>)arr[1]; LoggerProxy.debug(clz, valueList); LoggerProxy.debug(clz, sql); return update(sql,valueList,dialect,jdbcTemplate); } @Override public <T> List<T> in(InCondition inCondition) { Class<T> clz = inCondition.getClz(); Parsed parsed = Parser.get(clz); String inProperty = inCondition.getProperty(); if (StringUtil.isNullOrEmpty(inProperty)) { inProperty = parsed.getKey(X.KEY_ONE); } BeanElement be = parsed.getElementExisted(inProperty); String sql = MapperFactory.getSql(clz, Mapper.LOAD); String mapper = parsed.getMapper(inProperty); List<? extends Object> inList = inCondition.getInList(); sql = SqlUtil.buildIn(sql, mapper, be, inList); LoggerProxy.debug(clz, sql); return queryForList(sql, clz, null, this.dialect, jdbcTemplate); } @Override public Page<Map<String, Object>> find(Criteria.ResultMappedCriteria resultMapped) { Class clz = resultMapped.getClz(); SqlParsed sqlParsed = SqlUtil.fromCriteria(resultMapped, criteriaToSql, dialect); String sql = sqlParsed.getSql().toString(); LoggerProxy.debug(clz, sql); List<Map<String, Object>> list = queryForMapList(sql, resultMapped, this.dialect, jdbcTemplate); Page<Map<String, Object>> pagination = PageBuilder.build(resultMapped, list, () -> getCount(clz, sqlParsed.getCountSql(), resultMapped.getValueList())); return pagination; } @Override public List<Map<String, Object>> list(Criteria.ResultMappedCriteria resultMapped) { SqlParsed sqlParsed = SqlUtil.fromCriteria(resultMapped, criteriaToSql, dialect); String sql = sqlParsed.getSql().toString(); LoggerProxy.debug(resultMapped.getClz(), sql); return queryForMapList(sql, resultMapped, this.dialect, jdbcTemplate); } @Override public <K> List<K> listPlainValue(Class<K> clzz, Criteria.ResultMappedCriteria resultMapped){ SqlParsed sqlParsed = SqlUtil.fromCriteria(resultMapped, criteriaToSql, dialect); String sql = sqlParsed.getSql().toString(); LoggerProxy.debug(resultMapped.getClz(), sql); List<K> list = queryForPlainValueList(clzz,sql,resultMapped,this.dialect,jdbcTemplate); return list; } @Override public <T> T getOne(T conditionObj) { Class clz = conditionObj.getClass(); String sql = MapperFactory.getSql(clz, Mapper.LOAD); Parsed parsed = Parser.get(clz); Map<String, Object> queryMap = DataObjectConverter.objectToMapForQuery(parsed, conditionObj); sql = SqlUtil.concat(parsed, sql, queryMap); sql = SqlUtil.paged(sql, 1, 1, this.dialect); LoggerProxy.debug(clz, sql); if (queryMap.isEmpty()) throw new IllegalArgumentException("API of getOne(T) can't accept blank object: " + conditionObj); List<T> list = queryForList(sql, clz, queryMap.values(), this.dialect, jdbcTemplate); if (list.isEmpty()) return null; if (list.size() > 1) throw new TooManyResultsException("Expected one result (or null) to be returned by API of getOne(T), but found: " + list.size()); return list.get(0); } @Override public void findToHandle(Criteria.ResultMappedCriteria resultMapped, RowHandler<Map<String,Object>> handler) { Class clz = resultMapped.getClz(); SqlParsed sqlParsed = SqlUtil.fromCriteria(resultMapped, criteriaToSql, dialect); String sql = sqlParsed.getSql().toString(); LoggerProxy.debug(clz, sql); List<Object> valueList = resultMapped.getValueList(); queryForMapToHandle(clz, sql, valueList, dialect, jdbcTemplate, resultMapped, handler); } @Override public <T> void findToHandle(Criteria criteria, RowHandler<T> handler) { Class clz = criteria.getClz(); SqlParsed sqlParsed = SqlUtil.fromCriteria(criteria, criteriaToSql, dialect); String sql = sqlParsed.getSql().toString(); LoggerProxy.debug(clz, sql); List<Object> valueList = criteria.getValueList(); queryForMapToHandle(clz, sql, valueList, dialect, jdbcTemplate, null, handler); } private <T> void queryForMapToHandle(Class clzz, String sql, Collection<Object> valueList, Dialect dialect, JdbcTemplate jdbcTemplate, Criteria.ResultMappedCriteria resultMappedCriteria, RowHandler<T> handler) { Parsed parsed = Parser.get(clzz); RowMapper<Map<String, Object>> rowMapper = new ColumnMapRowMapper(); jdbcTemplate.query(connection -> { PreparedStatement preparedStatement = connection.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); preparedStatement.setFetchSize(50); try { preparedStatement.setFetchDirection(ResultSet.FETCH_FORWARD); }catch (SQLException e){ } if (valueList != null) { int i = 1; for (Object obj : valueList) { preparedStatement.setObject(i++, obj); } } return preparedStatement; }, resultSet -> { Map<String, Object> dataMap = rowMapper.mapRow(resultSet, 0); Map<String, Object> objectMap = DataObjectConverter.dataToPropertyObjectMap(clzz, dataMap, resultMappedCriteria, dialect); T t = null; if (resultMappedCriteria == null) { try { t = (T) clzz.newInstance(); DataObjectConverter.initObj(t, objectMap, parsed.getBeanElementList()); } catch (Exception e) { throw DaoExceptionTranslator.onQuery(e, logger); } } else { if(!resultMappedCriteria.isResultWithDottedKey()){ objectMap = BeanMapUtil.toJsonableMap(objectMap); } t = (T) objectMap; } if (t != null) { handler.handle(t); } }); } private boolean update(String sql, Collection<Object> list, Dialect dialect, JdbcTemplate jdbcTemplate) { try { Object[] arr = dialect.toArr(list); if (arr == null) return jdbcTemplate.update(sql) > 0; return jdbcTemplate.update(sql, arr) > 0; } catch (Exception e) { throw DaoExceptionTranslator.onRollback(null, e, logger); } } private <K> List<K> queryForPlainValueList(Class<K> clzz, String sql, Criteria.ResultMappedCriteria resultMappedCriteria, Dialect dialect, JdbcTemplate jdbcTemplate) { List<Object> valueList = resultMappedCriteria.getValueList(); if (valueList == null || valueList.isEmpty()) { return this.jdbcTemplate.query(sql, new SingleColumnRowMapper<>(clzz)); }else { Object[] arr = dialect.toArr(valueList); return this.jdbcTemplate.query(sql, arr, new SingleColumnRowMapper<>(clzz)); } } private List<Map<String, Object>> queryForMapList(String sql, Collection<Object> list, Dialect dialect, JdbcTemplate jdbcTemplate) { if (list == null || list.isEmpty()) { return jdbcTemplate.queryForList(sql); } else { Object[] arr = dialect.toArr(list); return jdbcTemplate.queryForList(sql, arr); } } private <T> List<T> queryForList(String sql, Class<T> clz, Collection<Object> list, Dialect dialect, JdbcTemplate jdbcTemplate) { List<Map<String, Object>> dataMapList = this.queryForMapList(sql, list, dialect, jdbcTemplate); List<Map<String, Object>> propertyMapList = DataObjectConverter.dataToPropertyObjectMapList(clz, dataMapList, null, dialect); List<T> tList = new ArrayList<>(); Parsed parsed = Parser.get(clz); try { for (Map<String, Object> map : propertyMapList) { T t = clz.newInstance(); DataObjectConverter.initObj(t, map, parsed.getBeanElementList()); tList.add(t); } } catch (Exception e) { throw DaoExceptionTranslator.onQuery(e, logger); } return tList; } private List<Map<String, Object>> queryForMapList(String sql, Criteria.ResultMappedCriteria resultMapped, Dialect dialect, JdbcTemplate jdbcTemplate) { List<Object> list = resultMapped.getValueList(); List<Map<String, Object>> dataMapList = queryForMapList(sql, list, dialect, jdbcTemplate); List<Map<String, Object>> propertyMapList = DataObjectConverter.dataToPropertyObjectMapList(resultMapped.getClz(), dataMapList, resultMapped, dialect); if (resultMapped.isResultWithDottedKey()) return propertyMapList; if (!propertyMapList.isEmpty()) return BeanMapUtil.toJsonableMapList(propertyMapList); return propertyMapList; } }