001/**
002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com).
003 * <p>
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * <p>
008 * http://www.apache.org/licenses/LICENSE-2.0
009 * <p>
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package io.jboot.db.model;
017
018import com.jfinal.kit.LogKit;
019import com.jfinal.log.Log;
020import com.jfinal.plugin.activerecord.*;
021import com.jfinal.plugin.activerecord.dialect.Dialect;
022import io.jboot.db.JbootDb;
023import io.jboot.db.SqlDebugger;
024import io.jboot.db.dialect.JbootDialect;
025import io.jboot.exception.JbootException;
026import io.jboot.exception.JbootIllegalConfigException;
027import io.jboot.utils.ClassUtil;
028import io.jboot.utils.StrUtil;
029
030import java.sql.Connection;
031import java.sql.PreparedStatement;
032import java.sql.SQLException;
033import java.sql.Statement;
034import java.util.*;
035
036
037/**
038 * @author michael yang
039 */
040public class JbootModel<M extends JbootModel<M>> extends Model<M> {
041
042    private static final Log LOG = Log.getLog(JbootModel.class);
043    private static final String DATASOURCE_CACHE_PREFIX = "__ds__";
044
045    private static JbootModelConfig config = JbootModelConfig.getConfig();
046    private static String column_created = config.getColumnCreated();
047    private static String column_modified = config.getColumnModified();
048    private static boolean idCacheEnable = config.isIdCacheEnable();
049
050    protected List<Join> joins = null;
051    String datasourceName = null;
052    String alias = null;
053    String loadColumns = null;
054    boolean isCopyModel = false;
055
056
057    public Joiner<M> leftJoin(String table) {
058        return joining(Join.TYPE_LEFT, table, true);
059    }
060
061    public Joiner<M> leftJoinIf(String table, boolean condition) {
062        return joining(Join.TYPE_LEFT, table, condition);
063    }
064
065    public Joiner<M> rightJoin(String table) {
066        return joining(Join.TYPE_RIGHT, table, true);
067    }
068
069    public Joiner<M> rightJoinIf(String table, boolean condition) {
070        return joining(Join.TYPE_RIGHT, table, condition);
071    }
072
073    public Joiner<M> innerJoin(String table) {
074        return joining(Join.TYPE_INNER, table, true);
075    }
076
077    public Joiner<M> innerJoinIf(String table, boolean condition) {
078        return joining(Join.TYPE_INNER, table, condition);
079    }
080
081    public Joiner<M> fullJoin(String table) {
082        return joining(Join.TYPE_FULL, table, true);
083    }
084
085    public Joiner<M> fullJoinIf(String table, boolean condition) {
086        return joining(Join.TYPE_FULL, table, condition);
087    }
088
089    /**
090     * set table alias in sql
091     *
092     * @param alias
093     * @return
094     */
095    public M alias(String alias) {
096        if (StrUtil.isBlank(alias)) {
097            throw new IllegalArgumentException("alias must not be null or empty.");
098        }
099        M model = getOrCopyDao();
100        model.alias = alias;
101        return model;
102    }
103
104
105    protected Joiner<M> joining(String type, String table, boolean condition) {
106        M model = getOrCopyDao();
107        if (model.joins == null) {
108            model.joins = new LinkedList<>();
109        }
110        Join join = new Join(type, table, condition);
111        model.joins.add(join);
112        return new Joiner<>(model, join);
113    }
114
115
116    /**
117     * set load columns in sql
118     *
119     * @param loadColumns
120     * @return
121     */
122    public M loadColumns(String loadColumns) {
123        if (StrUtil.isBlank(loadColumns)) {
124            throw new IllegalArgumentException("loadColumns must not be null or empty.");
125        }
126        M model = getOrCopyDao();
127        model.loadColumns = loadColumns;
128        return model;
129    }
130
131
132    public M distinct(String columnName) {
133        if (StrUtil.isBlank(columnName)) {
134            throw new IllegalArgumentException("columnName must not be null or empty.");
135        }
136        M dao = getOrCopyDao();
137        JbootModelExts.setDistinctColumn(dao, columnName);
138        return dao;
139    }
140
141
142    private M getOrCopyDao() {
143        if (isCopyModel) {
144            return (M) this;
145        } else {
146            M dao = copy()._setConfigName(datasourceName);
147            dao.isCopyModel = true;
148            return dao;
149        }
150    }
151
152    @Override
153    public M dao() {
154        put("__is_dao", true);
155        return (M) this;
156    }
157
158
159    private boolean isDaoModel() {
160        Boolean flag = getBoolean("__is_dao");
161        return flag != null && flag;
162    }
163
164    /**
165     * copy model with attrs or false
166     *
167     * @return
168     */
169    public M copy() {
170        M m = null;
171        try {
172            m = (M) _getUsefulClass().newInstance();
173            m.put(_getAttrs());
174
175            for (String attr : _getModifyFlag()) {
176                m._getModifyFlag().add(attr);
177            }
178        } catch (Exception e) {
179            LOG.error(e.toString(), e);
180        }
181        return m;
182    }
183
184    /**
185     * copy new model with db attrs and fill modifyFlag
186     *
187     * @return
188     */
189    public M copyModel() {
190        M m = null;
191        try {
192            m = (M) _getUsefulClass().newInstance();
193            Table table = _getTable(true);
194            Set<String> attrKeys = table.getColumnTypeMap().keySet();
195            for (String attrKey : attrKeys) {
196                Object o = this.get(attrKey);
197                if (o != null) {
198                    m.set(attrKey, o);
199                }
200            }
201        } catch (Exception e) {
202            LOG.error(e.toString(), e);
203        }
204        return m;
205    }
206
207
208    /**
209     * 修复 JFinal use 造成的线程安全问题
210     *
211     * @param configName
212     * @return
213     */
214    @Override
215    public M use(String configName) {
216        return use(configName, true);
217    }
218
219
220    /**
221     * 优先使用哪个数据源进行查询
222     *
223     * @param configNames
224     * @return
225     */
226    public M useFirst(String... configNames) {
227        if (configNames == null || configNames.length == 0) {
228            throw new IllegalArgumentException("configNames must not be null or empty.");
229        }
230
231        for (String name : configNames) {
232            M newDao = use(name, false);
233            if (newDao != null) {
234                return newDao;
235            }
236        }
237        return (M) this;
238    }
239
240
241    private M use(String configName, boolean validateDatasourceExist) {
242
243        //非 service 的 dao,例如 new User().user('ds').save()/upate()
244        if (!isDaoModel()) {
245            _setConfigName(configName);
246            return validDatasourceExist((M) this, validateDatasourceExist, configName);
247        }
248
249        //定义在 service 中的 DAO
250        M newDao = JbootModelExts.getDatasourceDAO(this, DATASOURCE_CACHE_PREFIX + configName);
251        if (newDao == null) {
252            newDao = this.copy()._setConfigName(configName);
253            newDao = validDatasourceExist(newDao, validateDatasourceExist, configName);
254            if (newDao != null) {
255                JbootModelExts.setDatasourceDAO(this, DATASOURCE_CACHE_PREFIX + configName, newDao);
256            }
257        }
258        return newDao;
259    }
260
261
262    private M validDatasourceExist(M model, boolean valid, String configName) {
263        if (model._getConfig() == null) {
264            if (valid) {
265                throw new JbootIllegalConfigException("The datasource \"" + configName + "\" not config well, please config it in jboot.properties.");
266            } else {
267                return null;
268            }
269        }
270        return model;
271    }
272
273
274    M _setConfigName(String configName) {
275        this.datasourceName = configName;
276        return (M) this;
277    }
278
279
280    @Override
281    protected Config _getConfig() {
282        if (datasourceName != null) {
283            return DbKit.getConfig(datasourceName);
284        }
285
286        String currentConfigName = JbootDb.getCurrentConfigName();
287        if (StrUtil.isNotBlank(currentConfigName)) {
288            Config config = DbKit.getConfig(currentConfigName);
289            if (config == null) {
290                LogKit.error("Can not use the datasource: {}, user default to replace.", currentConfigName);
291            } else {
292                return config;
293            }
294        }
295
296        return DbKit.getConfig(_getUsefulClass());
297    }
298
299
300    public boolean saveOrUpdate() {
301        if (null == _getIdValue()) {
302            return this.save();
303        }
304        return this.update();
305    }
306
307
308    @Override
309    public boolean save() {
310        if (_hasColumn(column_created) && get(column_created) == null) {
311            set(column_created, new Date());
312        }
313
314        // 生成主键,只对单一主键的表生成,如果是多主键,不生成。
315        String[] pkeys = _getPrimaryKeys();
316        if (pkeys != null && pkeys.length == 1 && get(pkeys[0]) == null) {
317            Object value = config.getPrimarykeyValueGenerator().genValue(this, _getPrimaryType());
318            if (value != null) {
319                set(pkeys[0], value);
320            }
321        }
322
323
324        filter(FILTER_BY_SAVE);
325
326        Config config = _getConfig();
327        Table table = _getTable();
328
329        StringBuilder sql = new StringBuilder();
330        List<Object> paras = new ArrayList<>();
331
332        Dialect dialect = _getConfig().getDialect();
333        dialect.forModelSave(table, _getAttrs(), sql, paras);
334
335        try {
336            return SqlDebugger.run(() -> {
337                Connection conn = null;
338                PreparedStatement pst = null;
339                int result = 0;
340                try {
341                    conn = config.getConnection();
342                    if (dialect.isOracle()) {
343                        pst = conn.prepareStatement(sql.toString(), table.getPrimaryKey());
344                    } else {
345                        pst = conn.prepareStatement(sql.toString(), Statement.RETURN_GENERATED_KEYS);
346                    }
347                    dialect.fillStatement(pst, paras);
348                    result = pst.executeUpdate();
349                    dialect.getModelGeneratedKey(this, pst, table);
350                    _getModifyFlag().clear();
351                    return result >= 1;
352                } finally {
353                    config.close(pst, conn);
354                }
355            }, config, sql.toString(), paras.toArray());
356        } catch (SQLException e) {
357            throw new ActiveRecordException(e);
358        }
359    }
360
361
362    @Override
363    protected void filter(int filterBy) {
364        config.getFilter().filter(this, filterBy);
365    }
366
367    @Override
368    public M findById(Object idValue) {
369        if (idValue == null) {
370            return null;
371        }
372        return idCacheEnable ? loadByCache(idValue) : super.findById(idValue);
373    }
374
375    /**
376     * 直接查询数据库,不走缓存
377     *
378     * @param idValue
379     * @return
380     */
381    public M findByIdWithoutCache(Object idValue) {
382        if (idValue == null) {
383            return null;
384        }
385        return super.findById(idValue);
386    }
387
388
389    @Override
390    public M findByIds(Object... idValues) {
391        if (idValues == null) {
392            return null;
393        }
394        if (idValues.length != _getPrimaryKeys().length) {
395            throw new IllegalArgumentException("idValues.length != _getPrimaryKeys().length");
396        }
397        return idCacheEnable ? loadByCache(idValues) : super.findByIds(idValues);
398    }
399
400    /**
401     * 直接查询数据库,不走缓存
402     *
403     * @param idValues
404     * @return
405     */
406    public M findByIdsWithoutCache(Object... idValues) {
407        if (idValues == null) {
408            return null;
409        }
410        if (idValues.length != _getPrimaryKeys().length) {
411            throw new IllegalArgumentException("idValues.length != _getPrimaryKeys().length");
412        }
413        return super.findByIds(idValues);
414    }
415
416
417    protected M loadByCache(Object... idValues) {
418        try {
419            M m = config.getIdCache().get(buildIdCacheName(_getTableName())
420                    , buildIdCacheKey(idValues)
421                    , () -> JbootModel.super.findByIds(idValues)
422                    , config.getIdCacheTime());
423            return m != null && config.isIdCacheByCopyEnable() ? m.copy() : m;
424        } catch (Exception ex) {
425            LOG.error("Jboot load model [" + ClassUtil.getUsefulClass(getClass()) + "] by cache is error, safe deleted it in cache.", ex);
426            safeDeleteCache(idValues);
427        }
428
429        return JbootModel.super.findByIds(idValues);
430    }
431
432
433    protected void safeDeleteCache(Object... idValues) {
434        try {
435            config.getIdCache().remove(buildIdCacheName(_getTableName())
436                    , buildIdCacheKey(idValues));
437        } catch (Exception ex) {
438            LOG.error("Remove cache is error by name [" + buildIdCacheName(_getTableName()) + "] and key [" + buildIdCacheKey(idValues) + "]", ex);
439        }
440    }
441
442
443    @Override
444    public boolean delete() {
445        boolean success = super.delete();
446        if (success && idCacheEnable) {
447            deleteIdCache();
448        }
449        return success;
450    }
451
452    @Override
453    public boolean deleteById(Object idValue) {
454        boolean success = super.deleteById(idValue);
455        if (success && idCacheEnable) {
456            deleteIdCacheById(idValue);
457        }
458        return success;
459    }
460
461    @Override
462    public boolean deleteByIds(Object... idValues) {
463        boolean success = super.deleteByIds(idValues);
464        if (success && idCacheEnable) {
465            deleteIdCacheById(idValues);
466        }
467        return success;
468    }
469
470
471    public boolean deleteByColumn(Column column) {
472        if (column == null || !column.checkAvailable()) {
473            throw new IllegalArgumentException("Column or value must not be null.");
474        }
475        return deleteByColumns(Columns.create(column));
476    }
477
478
479    public boolean deleteByColumns(Columns columns) {
480        processColumns(columns, "delete");
481
482        if (columns.isEmpty()) {
483            throw new IllegalArgumentException("Columns must not be null or empty.");
484        }
485        String sql = _getDialect().forDeleteByColumns(alias, joins, _getTableName(), columns.getList());
486        return Db.use(_getConfig().getName()).update(sql, Util.getValueArray(columns.getList())) >= 1;
487    }
488
489
490    public boolean deleteAll() {
491        Columns columns = Columns.create();
492
493        //通过 processColumns 可以重构 deleteAll 的行为
494        processColumns(columns, "deleteAll");
495
496        String sql = _getDialect().forDeleteByColumns(alias, joins, _getTableName(), columns.getList());
497        return Db.use(_getConfig().getName()).update(sql, Util.getValueArray(columns.getList())) >= 1;
498    }
499
500
501    public boolean batchDeleteByIds(Object... idValues) {
502        if (idValues == null || idValues.length == 0) {
503            return false;
504        }
505        boolean success = deleteByColumns(Columns.create().orEqs(_getPrimaryKey(), idValues));
506        if (success && idCacheEnable) {
507            for (Object id : idValues) {
508                deleteIdCacheById(id);
509            }
510        }
511        return success;
512    }
513
514
515    @Override
516    public boolean update() {
517        if (_hasColumn(column_modified)) {
518            set(column_modified, new Date());
519        }
520
521        boolean success = super.update();
522
523        if (success && idCacheEnable) {
524            deleteIdCache();
525        }
526
527        return success;
528    }
529
530
531    public void deleteIdCache() {
532        if (_getPrimaryKeys().length == 1) {
533            Object idValue = get(_getPrimaryKey());
534            deleteIdCacheById(idValue);
535        } else {
536            Object[] idvalues = new Object[_getPrimaryKeys().length];
537            for (int i = 0; i < idvalues.length; i++) {
538                idvalues[i] = get(_getPrimaryKeys()[i]);
539            }
540            deleteIdCacheById(idvalues);
541        }
542    }
543
544    public void deleteIdCacheById(Object... idvalues) {
545        safeDeleteCache(idvalues);
546    }
547
548
549    protected String buildIdCacheName(String orginal) {
550        return orginal;
551    }
552
553    protected String buildIdCacheKey(Object... idValues) {
554        if (idValues == null || idValues.length == 0) {
555            return null;
556        }
557
558        if (idValues.length == 1) {
559            return idValues[0].toString();
560        }
561
562        StringBuilder key = new StringBuilder();
563        for (int i = 0; i < idValues.length; i++) {
564            key.append(idValues[i]);
565            if (i < idValues.length - 1) {
566                key.append(":");
567            }
568        }
569        return key.toString();
570    }
571
572    protected JbootDialect _getDialect() {
573        Config config = _getConfig();
574        if (config == null) {
575            return throwCannotMappingException();
576        }
577        return (JbootDialect) config.getDialect();
578    }
579
580
581    private JbootDialect throwCannotMappingException() {
582        io.jboot.db.annotation.Table annotation = this.getClass().getAnnotation(io.jboot.db.annotation.Table.class);
583        if (annotation != null && StrUtil.isNotBlank(annotation.datasource())) {
584            throw new JbootException(
585                    String.format("Model \"%s\" can not mapping to datasource: " + annotation.datasource()
586                            , _getUsefulClass().getName()));
587        } else {
588            throw new JbootException(
589                    String.format("Model \"%s\" can not mapping to database table, maybe application cannot connect to database. "
590                            , _getUsefulClass().getName()));
591        }
592    }
593
594
595    public M findFirstByColumn(String column, Object value) {
596        return findFirstByColumn(Column.create(column, value));
597    }
598
599
600    public M findFirstByColumn(String column, Object value, String orderBy) {
601        return findFirstByColumn(Column.create(column, value), orderBy);
602    }
603
604    public M findFirstByColumn(Column column) {
605        if (column == null || !column.checkAvailable()) {
606//            throw new IllegalArgumentException("Column or value must not be null.");
607            return null;
608        }
609        return findFirstByColumns(Columns.create(column));
610    }
611
612
613    public M findFirstByColumn(Column column, String orderBy) {
614        if (column == null || !column.checkAvailable()) {
615//            throw new IllegalArgumentException("Column or value must not be null.");
616            return null;
617        }
618        return findFirstByColumns(Columns.create(column), orderBy);
619    }
620
621
622    public M findFirstByColumns(Columns columns) {
623        return findFirstByColumns(columns, null);
624    }
625
626
627    public M findFirstByColumns(Columns columns, String orderby) {
628        return findFirstByColumns(columns, orderby, null);
629    }
630
631    public M findFirstByColumns(Columns columns, String orderby, String loadColumns) {
632        processColumns(columns, "findFirst");
633        if (StrUtil.isBlank(loadColumns) && this.loadColumns != null) {
634            loadColumns = this.loadColumns;
635        }
636        if (StrUtil.isBlank(loadColumns)) {
637            loadColumns = "*";
638        }
639        String sql = _getDialect().forFindByColumns(alias, joins, _getTableName(), loadColumns, columns.getList(), orderby, 1);
640        return columns.isEmpty() ? findFirst(sql) : findFirst(sql, columns.getValueArray());
641    }
642
643
644    public List<M> findListByIds(Object... ids) {
645        if (ids == null || ids.length == 0) {
646            return null;
647        }
648
649        List<M> list = new ArrayList<>();
650        for (Object id : ids) {
651            if (id.getClass() == int[].class) {
652                findListByIds(list, (int[]) id);
653            } else if (id.getClass() == long[].class) {
654                findListByIds(list, (long[]) id);
655            } else if (id.getClass() == short[].class) {
656                findListByIds(list, (short[]) id);
657            } else {
658                M model = findById(id);
659                if (model != null) {
660                    list.add(model);
661                }
662            }
663        }
664        return list;
665    }
666
667    private void findListByIds(List<M> list, int[] ids) {
668        for (int id : ids) {
669            M model = findById(id);
670            if (model != null) {
671                list.add(model);
672            }
673        }
674    }
675
676    private void findListByIds(List<M> list, long[] ids) {
677        for (long id : ids) {
678            M model = findById(id);
679            if (model != null) {
680                list.add(model);
681            }
682        }
683    }
684
685
686    private void findListByIds(List<M> list, short[] ids) {
687        for (short id : ids) {
688            M model = findById(id);
689            if (model != null) {
690                list.add(model);
691            }
692        }
693    }
694
695
696    public List<M> findListByColumn(String column, Object value) {
697        return findListByColumn(Column.create(column, value), null, null);
698    }
699
700    public List<M> findListByColumn(Column column) {
701        return findListByColumn(column, null, null);
702    }
703
704
705    public List<M> findListByColumn(String column, Object value, Integer count) {
706        return findListByColumn(Column.create(column, value), null, count);
707    }
708
709    public List<M> findListByColumn(Column column, Integer count) {
710        return findListByColumn(column, null, count);
711    }
712
713
714    public List<M> findListByColumn(String column, Object value, String orderBy) {
715        return findListByColumn(Column.create(column, value), orderBy, null);
716    }
717
718
719    public List<M> findListByColumn(Column column, String orderby) {
720        return findListByColumn(column, orderby, null);
721    }
722
723    public List<M> findListByColumn(String column, Object value, String orderBy, Integer count) {
724        return findListByColumn(Column.create(column, value), orderBy, count);
725    }
726
727    public List<M> findListByColumn(Column column, String orderBy, Integer count) {
728        if (column == null || !column.checkAvailable()) {
729            return null;
730        }
731        return findListByColumns(Columns.create(column), orderBy, count);
732    }
733
734
735    public List<M> findListByColumns(List<Column> columns) {
736        return findListByColumns(columns, null, null);
737    }
738
739    public List<M> findListByColumns(List<Column> columns, String orderBy) {
740        return findListByColumns(columns, orderBy, null);
741    }
742
743    public List<M> findListByColumns(List<Column> columns, Integer count) {
744        return findListByColumns(columns, null, count);
745    }
746
747    public List<M> findListByColumns(List<Column> columns, String orderBy, Integer count) {
748        return findListByColumns(Columns.create(columns), orderBy, count);
749    }
750
751    public List<M> findListByColumns(Columns columns) {
752        return findListByColumns(columns, null, null);
753    }
754
755    public List<M> findListByColumns(Columns columns, String orderBy) {
756        return findListByColumns(columns, orderBy, null);
757    }
758
759    public List<M> findListByColumns(Columns columns, Integer count) {
760        return findListByColumns(columns, null, count);
761    }
762
763    public List<M> findListByColumns(Columns columns, String orderBy, Integer count) {
764        return findListByColumns(columns, orderBy, count, null);
765    }
766
767    public List<M> findListByColumns(Columns columns, String orderBy, Integer count, String loadColumns) {
768        processColumns(columns, "findList");
769        loadColumns = getLoadColumns(loadColumns);
770        String sql = _getDialect().forFindByColumns(alias, joins, _getTableName(), loadColumns, columns.getList(), orderBy, count);
771        return columns.isEmpty() ? find(sql) : find(sql, columns.getValueArray());
772    }
773
774    //方便在某些场景下,对 columns 进行二次加工
775    protected void processColumns(Columns columns, String action) {
776    }
777
778    @Override
779    protected Class<? extends Model> _getUsefulClass() {
780        Class c = getClass();
781        // guice : Model$$EnhancerByGuice$$40471411
782        // cglib : com.demo.blog.Blog$$EnhancerByCGLIB$$69a17158
783        // return c.getName().indexOf("EnhancerByCGLIB") == -1 ? c : c.getSuperclass();
784        // return c.getName().indexOf("$$EnhancerBy") == -1 ? c : c.getSuperclass();
785
786        //不支持匿名类,匿名无法被创建
787        return c.getName().indexOf("$") == -1 ? c : c.getSuperclass();
788    }
789
790    private String getLoadColumns(String loadColumns) {
791        if (StrUtil.isBlank(loadColumns) && StrUtil.isNotBlank(this.loadColumns)) {
792            loadColumns = this.loadColumns;
793        }
794
795        //使用 join 的情况下,需要判断 distinct
796        if (hasAnyJoinEffective()) {
797            String distinctColumn = JbootModelExts.getDistinctColumn(this);
798
799            //用户配置了 distinct
800            if (StrUtil.isNotBlank(distinctColumn)) {
801                if (StrUtil.isBlank(loadColumns)) {
802                    loadColumns = (StrUtil.isNotBlank(alias) ? alias : _getTableName()) + ".*";
803                }
804
805                //用户配置的 loadColumns 未包含 distinct 关键字
806                if (!loadColumns.toLowerCase().contains("distinct ")) {
807                    loadColumns = "DISTINCT " + distinctColumn + ", " + loadColumns;
808                }
809            }
810        }
811
812        if (StrUtil.isBlank(loadColumns)) {
813            loadColumns = "*";
814        }
815
816        return loadColumns;
817    }
818
819
820    boolean hasAnyJoinEffective() {
821        if (joins == null || joins.size() == 0) {
822            return false;
823        }
824
825        for (Join join : joins) {
826            if (join.isEffective()) {
827                return true;
828            }
829        }
830
831        return false;
832    }
833
834
835    public Page<M> paginate(int pageNumber, int pageSize) {
836        return paginateByColumns(pageNumber, pageSize, Columns.create(), null);
837    }
838
839
840    public Page<M> paginate(int pageNumber, int pageSize, String orderBy) {
841        return paginateByColumns(pageNumber, pageSize, Columns.create(), orderBy);
842    }
843
844
845    public Page<M> paginateByColumn(int pageNumber, int pageSize, Column column) {
846        return paginateByColumns(pageNumber, pageSize, Columns.create(column), null);
847    }
848
849
850    public Page<M> paginateByColumn(int pageNumber, int pageSize, Column column, String orderBy) {
851        return paginateByColumns(pageNumber, pageSize, Columns.create(column), orderBy);
852    }
853
854
855    public Page<M> paginateByColumns(int pageNumber, int pageSize, Columns columns) {
856        return paginateByColumns(pageNumber, pageSize, columns, null);
857    }
858
859
860    public Page<M> paginateByColumns(int pageNumber, int pageSize, List<Column> columns) {
861        return paginateByColumns(pageNumber, pageSize, columns, null);
862    }
863
864
865    public Page<M> paginateByColumns(int pageNumber, int pageSize, List<Column> columns, String orderBy) {
866        return paginateByColumns(pageNumber, pageSize, Columns.create(columns), orderBy);
867    }
868
869
870    public Page<M> paginateByColumns(int pageNumber, int pageSize, Columns columns, String orderBy) {
871        return paginateByColumns(pageNumber, pageSize, columns, orderBy, null);
872    }
873
874    public Page<M> paginateByColumns(int pageNumber, int pageSize, Columns columns, String orderBy, String loadColumns) {
875        processColumns(columns, "paginate");
876
877        loadColumns = getLoadColumns(loadColumns);
878
879
880        String selectPartSql = _getDialect().forPaginateSelect(loadColumns);
881        String fromPartSql = _getDialect().forPaginateFrom(alias, joins, _getTableName(), columns.getList(), orderBy);
882
883//        return columns.isEmpty()
884//                ? paginate(pageNumber, pageSize, selectPartSql, fromPartSql)
885//                : paginate(pageNumber, pageSize, selectPartSql, fromPartSql, columns.getValueArray());
886
887        Config config = _getConfig();
888        Connection conn = null;
889        try {
890            conn = config.getConnection();
891//            String totalRowSql = config.dialect.forPaginateTotalRow(select, sqlExceptSelect, this);
892            String totalRowSqlExceptSelect = _getDialect().forPaginateFrom(alias, joins, _getTableName(), columns.getList(), null);
893            String totalRowSql = config.getDialect().forPaginateTotalRow(selectPartSql, totalRowSqlExceptSelect, this);
894
895            StringBuilder findSql = new StringBuilder();
896            findSql.append(selectPartSql).append(' ').append(fromPartSql);
897
898            return doPaginateByFullSql(config, conn, pageNumber, pageSize, null, totalRowSql, findSql, columns.getValueArray());
899        } catch (Exception e) {
900            throw new ActiveRecordException(e);
901        } finally {
902            config.close(conn);
903        }
904    }
905
906
907    public long findCountByColumn(Column column) {
908        return findCountByColumns(Columns.create(column));
909    }
910
911
912    public long findCountByColumns(Columns columns) {
913        processColumns(columns, "findCount");
914
915        String loadColumns = "*";
916
917        //使用 distinct
918        if (hasAnyJoinEffective()) {
919            String distinctColumn = JbootModelExts.getDistinctColumn(this);
920            if (StrUtil.isNotBlank(distinctColumn)) {
921                loadColumns = "DISTINCT " + distinctColumn;
922            }
923        }
924
925
926        String sql = _getDialect().forFindCountByColumns(alias, joins, _getTableName(), loadColumns, columns.getList());
927        Long value = Db.use(_getConfig().getName()).queryLong(sql, Util.getValueArray(columns.getList()));
928        return value == null ? 0 : value;
929    }
930
931
932    public <T> T _getIdValue() {
933        return get(_getPrimaryKey());
934    }
935
936
937    public Object[] _getIdValues() {
938        String[] pkeys = _getPrimaryKeys();
939        Object[] values = new Object[pkeys.length];
940
941        int i = 0;
942        for (String key : pkeys) {
943            values[i++] = get(key);
944        }
945        return values;
946    }
947
948
949    public String _getTableName() {
950        return _getTable(true).getName();
951    }
952
953    @Override
954    public Table _getTable() {
955        return _getTable(false);
956    }
957
958    private transient Table table;
959
960    public Table _getTable(boolean validateMapping) {
961        if (table == null) {
962            table = super._getTable();
963            if (table == null && validateMapping) {
964                throwCannotMappingException();
965            }
966        }
967        return table;
968    }
969
970
971    public String _getPrimaryKey() {
972        return _getPrimaryKeys()[0];
973    }
974
975    private transient String[] primaryKeys;
976
977    public String[] _getPrimaryKeys() {
978        if (primaryKeys != null) {
979            return primaryKeys;
980        }
981        primaryKeys = _getTable(true).getPrimaryKey();
982
983        if (primaryKeys == null) {
984            throw new JbootException(String.format("primaryKeys == null in [%s]", getClass()));
985        }
986        return primaryKeys;
987    }
988
989
990    private transient Class<?> primaryType;
991
992    protected Class<?> _getPrimaryType() {
993        if (primaryType == null) {
994            primaryType = _getTable(true).getColumnType(_getPrimaryKey());
995        }
996        return primaryType;
997    }
998
999
1000    protected boolean _hasColumn(String columnLabel) {
1001        return _getTable(true).hasColumnLabel(columnLabel);
1002    }
1003
1004
1005    @Override
1006    public boolean equals(Object o) {
1007        if (!(o instanceof JbootModel)) {
1008            return false;
1009        }
1010
1011        //可能model在rpc的Controller层,没有映射到数据库
1012        if (_getTable(false) == null) {
1013            return this == o;
1014        }
1015
1016        Object id = ((JbootModel<?>) o)._getIdValue();
1017        return id != null && id.equals(this._getIdValue());
1018    }
1019
1020
1021    @Override
1022    public int hashCode() {
1023        //可能model在rpc的Controller层,没有映射到数据库
1024        if (_getTable(false) == null) {
1025            return Objects.hash(_getAttrValues());
1026        }
1027
1028        final Object[] idValues = _getIdValues();
1029        return idValues.length > 0 ? Objects.hash(idValues) : Objects.hash(_getAttrValues());
1030    }
1031
1032    public M preventXssAttack() {
1033        String[] attrNames = _getAttrNames();
1034        for (String attrName : attrNames) {
1035            Object value = get(attrName);
1036            if (!(value instanceof String)) {
1037                continue;
1038            }
1039
1040            set(attrName, StrUtil.escapeHtml((String) value));
1041        }
1042        return (M) this;
1043    }
1044
1045
1046    public M preventXssAttack(String... ignoreAttrs) {
1047        String[] attrNames = _getAttrNames();
1048        for (String attrName : attrNames) {
1049            Object value = get(attrName);
1050            if (!(value instanceof String)) {
1051                continue;
1052            }
1053
1054            boolean isIgnoreAttr = false;
1055            for (String ignoreAttr : ignoreAttrs) {
1056                if (attrName.equals(ignoreAttr)) {
1057                    isIgnoreAttr = true;
1058                    break;
1059                }
1060            }
1061
1062            if (!isIgnoreAttr) {
1063                set(attrName, StrUtil.escapeHtml((String) value));
1064            }
1065        }
1066
1067        return (M) this;
1068    }
1069
1070
1071    /**
1072     * Override for print sql
1073     *
1074     * @param config
1075     * @param conn
1076     * @param sql
1077     * @param paras
1078     * @return
1079     * @throws Exception
1080     */
1081    @Override
1082    protected List<M> find(Config config, Connection conn, String sql, Object... paras) throws Exception {
1083        return SqlDebugger.run(() -> {
1084            try {
1085                return super.find(config, conn, sql, paras);
1086            } catch (Exception e) {
1087                if (e instanceof SQLException) {
1088                    throw (SQLException) e;
1089                } else {
1090                    throw new SQLException(e);
1091                }
1092            }
1093        }, config, sql, paras);
1094    }
1095
1096}