diff --git a/src/main/java/com/sondertara/joya/jpa/repository/BaseRepositoryImpl.java b/src/main/java/com/sondertara/joya/jpa/repository/BaseRepositoryImpl.java index 04c1b6b..70de820 100644 --- a/src/main/java/com/sondertara/joya/jpa/repository/BaseRepositoryImpl.java +++ b/src/main/java/com/sondertara/joya/jpa/repository/BaseRepositoryImpl.java @@ -7,8 +7,7 @@ import com.sondertara.joya.utils.BeanUtil; import com.sondertara.joya.utils.SqlUtils; import org.apache.commons.beanutils.BeanUtils; -import org.hibernate.Session; -import org.hibernate.query.internal.NativeQueryImpl; +import org.hibernate.query.NativeQuery; import org.hibernate.transform.Transformers; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; @@ -77,7 +76,7 @@ public S saveIgnoreNull(@NonNull S entity) { } else { T po = super.findById(((ID) Objects.requireNonNull(eif.getId(entity)))).orElse(null); if (Objects.nonNull(po)) { - BeanUtil.copyProperties(entity, po); + BeanUtil.copyPropertiesIgnoreNull(entity, po); return (S) this.em.merge(po); } return this.em.merge(entity); @@ -140,39 +139,34 @@ private Map toMap(List list) { @Override - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "deprecation"}) public List findListBySql(String sql, Class clazz, Object... params) { - Query query = em.unwrap(Session.class).createNativeQuery(sql); + Query query = em.createNativeQuery(sql); setParameters(query, params); - return query.unwrap(NativeQueryImpl.class).setResultTransformer(new AliasToBeanTransformer<>(clazz)).list(); + return query.unwrap(NativeQuery.class).setResultTransformer(new AliasToBeanTransformer<>(clazz)).list(); } @Override - @SuppressWarnings("unchecked") public List findListBySql(NativeSqlQuery nativeSql, Class resultClass) { - Query query = em.unwrap(Session.class).createNativeQuery(nativeSql.toSql()); - setParameters(query, nativeSql.getParams()); - return query.unwrap(NativeQueryImpl.class).setResultTransformer(new AliasToBeanTransformer<>(resultClass)).list(); + return findListBySql(nativeSql.toSql(), resultClass, null != nativeSql.getParams() ? nativeSql.getParams().toArray() : new Object[0]); } @Override - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "deprecation"}) public Page queryPageBySql(NativeSqlQuery nativeSql, Class resultClass, Integer pageNo, Integer pageSize) { String sqlStr = nativeSql.toSql(); //获取总记录数 - Session session = em.unwrap(Session.class); String countSql = SqlUtils.buildCountSql(sqlStr); - Query countQuery = session.createNativeQuery(countSql); + Query countQuery = em.createNativeQuery(countSql); setParameters(countQuery, nativeSql.getParams()); long totalRecord = ((Number) countQuery.getSingleResult()).longValue(); //获取分页结果 - Query pageQuery = session.createNativeQuery(sqlStr); + Query pageQuery = em.createNativeQuery(sqlStr); setParameters(pageQuery, nativeSql.getParams()); - - List result = totalRecord == 0 ? new ArrayList<>(0) : pageQuery.setFirstResult(pageNo).setMaxResults(pageSize).unwrap(NativeQueryImpl.class).setResultTransformer(new AliasToBeanTransformer<>(resultClass)).list(); + List result = totalRecord == 0 ? new ArrayList<>(0) : pageQuery.setFirstResult(pageNo).setMaxResults(pageSize).unwrap(NativeQuery.class).setResultTransformer(new AliasToBeanTransformer<>(resultClass)).list(); return new PageImpl<>(result, PageRequest.of(pageNo, pageSize), totalRecord); } @@ -190,7 +184,7 @@ public T findOneByHql(final String ql, final Object... params) { } public List findAllByHql(final String ql, final Object... params) { - return findAllByHql(ql, (Pageable) null, params); + return findAllByHql(ql, null, params); } /** @@ -249,7 +243,7 @@ private String prepareOrder(Sort sort) { * 按顺序设置Query参数 */ private void setParameters(Query query, Object[] params) { - if (params != null) { + if (params != null && params.length > 0) { for (int i = 0; i < params.length; i++) { query.setParameter(i + 1, params[i]); } @@ -260,7 +254,7 @@ private void setParameters(Query query, Object[] params) { * 按顺序设置Query参数 */ private void setParameters(Query query, List params) { - if (params != null) { + if (params != null && params.size() > 0) { for (int i = 0; i < params.size(); i++) { query.setParameter(i + 1, params.get(i)); } @@ -290,47 +284,45 @@ public X findListByHql(String queryJql, Map params) { @Override public List> findMapListBySql(NativeSqlQuery nativeSql, boolean camelCase) { - - return findMapListBySql(nativeSql, camelCase); + return findMapListBySql(nativeSql.toSql(), nativeSql.getParams(), camelCase); } @Override public Map findMapBySql(NativeSqlQuery nativeSql, boolean camelCase) { - - return findMapBySql(nativeSql, camelCase); + return findMapBySql(nativeSql.toSql(), nativeSql.getParams(), camelCase); } /** * (non-Javadoc) */ @Override - @SuppressWarnings({"unchecked"}) + @SuppressWarnings({"unchecked", "deprecation"}) public List> findMapListBySql(String querySql, List params, boolean camelCase) { Query query = em.createNativeQuery(querySql); setParameters(query, params); - query.unwrap(NativeQueryImpl.class).setResultTransformer(AliasToMapResultTransformer.getInstance(camelCase)); + query.unwrap(NativeQuery.class).setResultTransformer(AliasToMapResultTransformer.getInstance(camelCase)); return query.getResultList(); } @Override - @SuppressWarnings({"unchecked"}) + @SuppressWarnings({"unchecked", "deprecation"}) public Map findMapBySql(String querySql, List params, boolean camelCase) { Query query = em.createNativeQuery(querySql); setParameters(query, params); - query.unwrap(NativeQueryImpl.class).setResultTransformer(AliasToMapResultTransformer.getInstance(camelCase)); + query.unwrap(NativeQuery.class).setResultTransformer(AliasToMapResultTransformer.getInstance(camelCase)); return (Map) query.getSingleResult(); } - @SuppressWarnings({"unchecked"}) + @SuppressWarnings({"unchecked", "deprecation"}) @Override public List> findMapListByHql(String querySql, Map params) { Query query = this.createSqlQueryWithNameParam(querySql, params); - query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP); + query.unwrap(NativeQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP); return query.getResultList(); } - @SuppressWarnings("unchecked") @Override + @SuppressWarnings({"unchecked"}) public List findListBySql(String querySql, Map params) { Query query = this.createSqlQueryWithNameParam(querySql, params); return query.getResultList(); @@ -341,7 +333,7 @@ public List findListBySql(String querySql, Map params) { * * @param values 参数Map */ - @SuppressWarnings({"rawtypes", "unchecked"}) + @SuppressWarnings({"unchecked"}) public Query createQueryWithNameParam(final String queryJql, final Map values) { Query query = this.em.createQuery(queryJql); @@ -392,13 +384,13 @@ public X findObjectBySql(String querySql, Class clazz, final Map findMapBySql(String querySql, Map params) { Query query = this.createSqlQueryWithNameParam(querySql, params); - query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP); + query.unwrap(NativeQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP); return (Map) query.getSingleResult(); } diff --git a/src/main/java/com/sondertara/joya/jpa/repository/JoyaRepository.java b/src/main/java/com/sondertara/joya/jpa/repository/JoyaRepository.java index 2166e14..dabd7a8 100644 --- a/src/main/java/com/sondertara/joya/jpa/repository/JoyaRepository.java +++ b/src/main/java/com/sondertara/joya/jpa/repository/JoyaRepository.java @@ -1,29 +1,43 @@ package com.sondertara.joya.jpa.repository; +import com.sondertara.common.util.CollectionUtils; +import com.sondertara.common.util.StringFormatter; +import com.sondertara.joya.cache.TableClassCache; +import com.sondertara.joya.core.model.TableEntity; import com.sondertara.joya.core.query.NativeSqlQuery; import com.sondertara.joya.core.query.criterion.JoinCriterion; import com.sondertara.joya.core.query.pagination.JoyaPageConvert; import com.sondertara.joya.core.query.pagination.PageQueryParam; import com.sondertara.joya.core.query.pagination.PageResult; +import com.sondertara.joya.domain.PersistEntity; import com.sondertara.joya.ext.JoyaSpringContext; import com.sondertara.joya.hibernate.transformer.AliasToBeanTransformer; import com.sondertara.joya.hibernate.transformer.AliasToMapResultTransformer; +import com.sondertara.joya.jpa.repository.statment.SimpleBatchPreparedStatementSetter; import com.sondertara.joya.utils.SqlUtils; -import org.hibernate.query.internal.NativeQueryImpl; +import org.hibernate.Session; +import org.hibernate.query.NativeQuery; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.jdbc.support.JdbcUtils; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import javax.persistence.EntityManager; import javax.persistence.Query; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.StringJoiner; import java.util.function.UnaryOperator; +import java.util.stream.Collectors; /** * dao @@ -35,6 +49,9 @@ public class JoyaRepository { private static final Logger log = LoggerFactory.getLogger(JoyaRepository.class); private static final String SQL_VIEW_SWITCH = "joya.sql-view"; + + private static final int BATCH_SIZE = 500; + private final EntityManager em; public JoyaRepository(EntityManager em) { @@ -45,20 +62,20 @@ public JoyaRepository(EntityManager em) { /** * 查询list * - * @param sql 原生sql - * @param clazz 目标实体 - * @param params 参数 - * @param 泛型 + * @param sql 原生sql + * @param resultClass 目标实体 + * @param params 参数 + * @param 泛型 * @return list */ - @SuppressWarnings("unchecked") - public List findListBySql(String sql, Class clazz, Object... params) { + @SuppressWarnings({"unchecked","deprecation"}) + public List findListBySql(String sql, Class resultClass, Object... params) { if (JoyaSpringContext.getConfig(SQL_VIEW_SWITCH, true)) { log.info("[findListBySql] SQL:\nJoya-SQL: {}", sql); } Query query = em.createNativeQuery(sql); setParameters(query, params); - return query.unwrap(NativeQueryImpl.class).setResultTransformer(new AliasToBeanTransformer<>(clazz)).list(); + return query.unwrap(NativeQuery.class).setResultTransformer(new AliasToBeanTransformer<>(resultClass)).list(); } /** @@ -69,7 +86,7 @@ public List findListBySql(String sql, Class clazz, Object... params) { * @param generic * @return list */ - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked","deprecation"}) public List findListBySql(NativeSqlQuery nativeSql, Class resultClass) { if (JoyaSpringContext.getConfig(SQL_VIEW_SWITCH, true)) { @@ -77,7 +94,7 @@ public List findListBySql(NativeSqlQuery nativeSql, Class resultClass) } Query query = em.createNativeQuery(nativeSql.toSql()); setParameters(query, nativeSql.getParams()); - return query.unwrap(NativeQueryImpl.class).setResultTransformer(new AliasToBeanTransformer<>(resultClass)).list(); + return query.unwrap(NativeQuery.class).setResultTransformer(new AliasToBeanTransformer<>(resultClass)).list(); } /** @@ -109,19 +126,19 @@ public Map findMapBySql(NativeSqlQuery nativeSql, List p /** * (non-Javadoc) */ - @SuppressWarnings({"unchecked"}) + @SuppressWarnings({"unchecked","deprecation"}) public List> findMapListBySql(String querySql, List params, boolean camelCase) { Query query = em.createNativeQuery(querySql); setParameters(query, params); - query.unwrap(NativeQueryImpl.class).setResultTransformer(AliasToMapResultTransformer.getInstance(camelCase)); + query.unwrap(NativeQuery.class).setResultTransformer(AliasToMapResultTransformer.getInstance(camelCase)); return query.getResultList(); } - @SuppressWarnings({"unchecked"}) + @SuppressWarnings({"unchecked","deprecation"}) public Map findMapBySql(String querySql, List params, boolean camelCase) { Query query = em.createNativeQuery(querySql); setParameters(query, params); - query.unwrap(NativeQueryImpl.class).setResultTransformer(AliasToMapResultTransformer.getInstance(camelCase)); + query.unwrap(NativeQuery.class).setResultTransformer(AliasToMapResultTransformer.getInstance(camelCase)); return (Map) query.getSingleResult(); } @@ -156,43 +173,55 @@ public PageResult queryPage(PageQueryParam queryParam, Class resultCla } + /** * query page * - * @param nativeSql sql query + * @param sql sql query * @param resultClass result class * @param pageNo page start * @param pageSize page size * @param the type of result + * @param params the query params * @return pagination result */ - @SuppressWarnings("unchecked") - public PageResult queryPage(NativeSqlQuery nativeSql, Class resultClass, Integer pageNo, Integer pageSize) { + @SuppressWarnings({"unchecked","deprecation"}) + public PageResult queryPage(String sql, Class resultClass, Integer pageNo, Integer pageSize, Object... params) { Boolean opened = JoyaSpringContext.getConfig(SQL_VIEW_SWITCH, false); - - String sqlStr = nativeSql.toSql(); - if (opened) { - log.info("[queryPage] SQL:\nJoya-SQL: {}", sqlStr); + log.info("[queryPage] SQL:\nJoya-SQL: {}", sql); } - // get the count sql - String countSql = SqlUtils.buildCountSql(sqlStr); + String countSql = SqlUtils.buildCountSql(sql); Query countQuery = em.createNativeQuery(countSql); - setParameters(countQuery, nativeSql.getParams()); + setParameters(countQuery, params); long totalRecord = ((Number) countQuery.getSingleResult()).longValue(); // the query - Query pageQuery = em.createNativeQuery(sqlStr); + Query pageQuery = em.createNativeQuery(sql); - setParameters(pageQuery, nativeSql.getParams()); + setParameters(pageQuery, params); - List result = totalRecord == 0 ? new ArrayList<>(0) : pageQuery.setFirstResult(pageNo).setMaxResults(pageSize).unwrap(NativeQueryImpl.class).setResultTransformer(new AliasToBeanTransformer<>(resultClass)).list(); + List result = totalRecord == 0 ? new ArrayList<>(0) : pageQuery.setFirstResult(pageNo * pageSize).setMaxResults(pageSize).unwrap(NativeQuery.class).setResultTransformer(new AliasToBeanTransformer<>(resultClass)).list(); return new PageResult<>(pageNo, pageSize, totalRecord, result); } + /** + * query page + * + * @param nativeSql sql query + * @param resultClass result class + * @param pageNo page start + * @param pageSize page size + * @param the type of result + * @return pagination result + */ + public PageResult queryPage(NativeSqlQuery nativeSql, Class resultClass, Integer pageNo, Integer pageSize) { + return queryPage(nativeSql.toSql(), resultClass, pageNo, pageSize, null == nativeSql.getParams() ? new Object[0] : nativeSql.getParams().toArray()); + } + /** * 根据ql和按照索引顺序的params查询一个实体 @@ -264,7 +293,7 @@ private String prepareOrder(Sort sort) { * 按顺序设置Query参数 */ private void setParameters(Query query, Object[] params) { - if (params != null) { + if (params != null && params.length > 0) { for (int i = 0; i < params.length; i++) { query.setParameter(i + 1, params[i]); } @@ -281,4 +310,107 @@ private void setParameters(Query query, List params) { } } } + + /** + * Batch update, first execute the update operation, then the insert operation, + * it is not performed in the transaction by default, and support Spring transaction management + * 批量更新,先执行更新操作,后执行插入操作,默认不是在事务中执行,支持Spring事务管理 + *

+ * The pojo need extends {@link PersistEntity},and explicitly specifies whether it is new by the field isNew + * 实体需要继承{@link PersistEntity} 通过isNew字段来表示是否是新建还是更新,默认是新增 + *

+ * 1.对于mysql主键自增这种实体通过重写{@link PersistEntity#isNew()}判断主键是否为null就可以标识是否新增 + * 2.对于通过序列来设置主键值的情形,在构造dataList时,需要显示调用{@link PersistEntity#setNew(boolean)} 来标识是否新增 + * + * @param dataList 数据 + * @param 类型 + */ + @Transactional(readOnly = false, propagation = Propagation.SUPPORTS) + public void batchUpdate(List dataList) throws SQLException { + try { + if (CollectionUtils.isEmpty(dataList)) { + return; + } + List insertList = new ArrayList<>(); + List updateList = new ArrayList<>(); + for (T data : dataList) { + TableEntity table = TableClassCache.getInstance().getTable(data, true); + if (data.isNew() || table.getData().get(table.getPrimaryKey()) == null) { + if (table.getData().get(table.getPrimaryKey()) == null) { + table.getData().remove(table.getPrimaryKey()); + } + insertList.add(table); + } else { + updateList.add(table); + } + } + String insertSql = null; + if (insertList.size() > 0) { + TableEntity table = insertList.get(0); + Map data = table.getData(); + // auto increment + if (null == data.get(table.getPrimaryKey())) { + data.remove(table.getPrimaryKey()); + } + List set = data.keySet().stream().map(s -> "?").collect(Collectors.toList()); + insertSql = StringFormatter.format("insert into {}({}) values({})", table.getTableName(), String.join(",", data.keySet()), String.join(",", set)); + } + String updateSql = null; + if (updateList.size() > 0) { + TableEntity table = updateList.get(0); + StringJoiner sj = new StringJoiner(", "); + Map map = table.getData(); + Object primaryKeyValue = map.remove(table.getPrimaryKey()); + for (String key : map.keySet()) { + sj.add(key + " = ?"); + } + map.put(table.getPrimaryKey(), primaryKeyValue); + updateSql = StringFormatter.format("update {} set {} where {} = ?", table.getTableName(), sj.toString(), table.getPrimaryKey()); + } + try (Session session = em.unwrap(Session.class)) { + String finalUpdateSql = updateSql; + String finalInsertSql = insertSql; + log.info("Batch update start,insert size is:{},update size is:{}", insertList.size(), updateList.size()); + session.doWork(connection -> { + if (null != finalUpdateSql) { + batchUpdate(finalUpdateSql, updateList, connection); + } + if (null != finalInsertSql) { + batchUpdate(finalInsertSql, insertList, connection); + } + }); + } + } catch (Exception e) { + throw new SQLException(e.getMessage(), e.getCause()); + } + + log.info("Batch update completed."); + } + + private void batchUpdate(String sql, List rows, Connection connection) throws SQLException { + if (null == sql) { + return; + } + SimpleBatchPreparedStatementSetter setter = new SimpleBatchPreparedStatementSetter(rows); + PreparedStatement ps = connection.prepareStatement(sql); + int batchSize = setter.getBatchSize(); + if (JdbcUtils.supportsBatchUpdates(ps.getConnection())) { + for (int j = 0; j < batchSize; j++) { + setter.setValues(ps, j); + ps.addBatch(); + if ((j + 1) % BATCH_SIZE == 0 || j == batchSize - 1) { + ps.executeBatch(); + ps.clearBatch(); + } + } + } else { + for (int i = 0; i < batchSize; i++) { + setter.setValues(ps, i); + ps.executeUpdate(); + } + } + if (!ps.isClosed()) { + ps.close(); + } + } }