#3249 Refactor DAO layer for modular architecture#3252
Conversation
Patrykb0802
commented
Mar 12, 2026
- introduce and complete DAO interface contracts across core modules
- align DAO implementations, caches and services with interface-based access
- replace scattered DAO factory usage with centralized database-aware bean resolution
- introduce and complete DAO interface contracts across core modules - align DAO implementations, caches and services with interface-based access - replace scattered DAO factory usage with centralized database-aware bean resolution
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
| else{ | ||
| StreamUtils.transfer(rs.getBlob(fieldName).getBinaryStream(),out); | ||
| } | ||
| StreamUtils.transfer(ApplicationBeans.getBinaryDataHandler().getBinaryStream(rs, fieldName), out); |
There was a problem hiding this comment.
What's the point of this change? We try to minimize changes unless they're necessary.
| @Override | ||
| public DatabaseType getType() { | ||
| return DatabaseType.DERBY; | ||
| public String getTypeKey() { |
There was a problem hiding this comment.
We never do things like that. A specific type is always better than a generic one, and it contains a lot of information about what it is and how to use it. We also don't change the old API.
| if (is == null) | ||
| return null; | ||
| } | ||
| InputStream is = ApplicationBeans.getBinaryDataHandler().getBinaryStream(rs, 1); |
There was a problem hiding this comment.
What is the purpose of this change? We strive to minimize changes unless they are necessary.
| upgrade.upgrade(); | ||
| new SystemSettingsDAO().setValue( | ||
| ISystemSettingsDAO systemSettingsDAO = org.scada_lts.web.beans.ApplicationBeans.getSystemSettingsDAOBean(); | ||
| systemSettingsDAO.setValue( |
There was a problem hiding this comment.
getSystemSettingsDAOBean -> getSystemSettingsDaoBean (camel)
| } | ||
|
|
||
| abstract public DatabaseType getType(); | ||
| abstract public String getTypeKey(); |
There was a problem hiding this comment.
We never make such changes, the concrete enum type is perfectly suited for this task and this type should remain in use. Additionally, we are not changing the API in such a radical way.
| // Keep DAO query free from NOT IN to avoid SQL-dialect issues. | ||
| List<DataPointVO> points = dataPointService.getDataPoints(searchDataPointJson.getKeywordSearch(), | ||
| searchDataPointJson.getExcludeIds(), searchDataPointJson.isStartsWith(), | ||
| null, searchDataPointJson.isStartsWith(), |
There was a problem hiding this comment.
Have you changed the logic here? Exclude id should appear in both cases. This is optimization.
| || (searchDataPointJson.getSettable() != null); | ||
| boolean hasExcludeIds = | ||
| searchDataPointJson.getExcludeIds() != null && !searchDataPointJson.getExcludeIds().isEmpty(); | ||
| if(!hasTypeOrSettableFilter && !hasExcludeIds) { |
There was a problem hiding this comment.
Changing the logic. This is an optimization because we can't filter the sql query by the settable, dataType field, so we only apply the optimization if it's not set.
| new int[] { | ||
| Common.getEnvironmentProfile().getString("db.type") | ||
| .equals("postgres") ? Types.BINARY : Types.BLOB, | ||
| ApplicationBeans.getBinaryDataHandler().getBinarySqlType(), |
There was a problem hiding this comment.
Instead of creating a separate class to only set the type, the type can be passed to a method in the enum
|
|
||
| private static DatabaseAccess resolveDatabaseAccess(String dbKey) { | ||
| String normalized = normalizeDbKey(dbKey); | ||
| String beanName = "databaseAccess-" + normalized; |
There was a problem hiding this comment.
Basing bean customization on your own naming conventions hard code is not a good practice, it is better to base it on the type, so that Spring itself determines the correct bean.
| data.put("filedataCount", 0); | ||
| String dbType = checkTypeDB(); | ||
| if (dbType.equalsIgnoreCase("mysql") || dbType.equalsIgnoreCase("postgres")) { | ||
| double size = ApplicationBeans.getSystemSettingsDAOBean().getDataBaseSize(); |
| DataPointService dataPointService = new DataPointService(); | ||
| DataPointVO dataPoint = dataPointService.getDataPoint(dataPointId); | ||
| DwrResponseI18n response = super.toggleDataPoint(dataPoint); | ||
| DwrResponseI18n response = super.toggleDataPointInternal(dataPoint); |
| ViewHierarchyService service = ApplicationBeans.getViewHierarchyServiceBean(); | ||
|
|
||
| cache = service.getAll(); | ||
| cache = new ViewHierarchyService().getAll(); |
There was a problem hiding this comment.
ViewHierarchyService service = new ViewHierarchyService();
cache = service.getAll();
| } | ||
|
|
||
| @Override | ||
| public List<PointEventDetectorVO> getPointEventDetectors(long limit, int offset) { |
There was a problem hiding this comment.
Why such changes? Besides, there's a reason the init method refers directly to the DAO; it shouldn't be accessible outside the init method; caching doesn't work there.
| ConfigurationDB.useOracle11gDB(); | ||
| } else { | ||
| ConfigurationDB.useDerbyDB(); | ||
| String dbKey = DatabaseType.from(databaseType).getKey(); |
There was a problem hiding this comment.
use switch with enum:
DatabaseType dbKey = DatabaseType.from(databaseType);
switch (dbKey) {
case MYSQL:
ConfigurationDB.useMysqlDB();
break;
case POSTGRES:
ConfigurationDB.usePostgresDB();
break;
case MSSQL:
ConfigurationDB.useMssqlDB();
break;
case ORACLE11G:
ConfigurationDB.useOracle11gDB();
break;
default:
ConfigurationDB.useDerbyDB();
break;
}
But it can be moved to enum under one method e.g. use() and different implementations depending on the enum instance:
public enum DatabaseType {
DERBY {
@Override
DatabaseAccess getImpl() {
return new DerbyAccess();
}
@Override
public void use() {
ConfigurationDB.useDerbyDB();
}
},
MSSQL {
@Override
DatabaseAccess getImpl() {
return new MSSQLAccess();
}
@Override
public void use() {
ConfigurationDB.useMssqlDB();
}
},
MYSQL {
@Override
DatabaseAccess getImpl() {
return new MySQLAccess();
}
@Override
public InputStream getBinaryStream(ResultSet rs, int columnIndex) throws SQLException {
Blob blob = rs.getBlob(columnIndex);
return blob == null ? null : blob.getBinaryStream();
}
@Override
public InputStream getBinaryStream(ResultSet rs, String columnLabel) throws SQLException {
Blob blob = rs.getBlob(columnLabel);
return blob == null ? null : blob.getBinaryStream();
}
@Override
public void use() {
ConfigurationDB.useMysqlDB();
}
},
POSTGRES {
@Override
DatabaseAccess getImpl() {
return new PostgreSQLAccess();
}
@Override
public int getBinarySqlType() {
return Types.BINARY;
}
@Override
public void use() {
ConfigurationDB.usePostgresDB();
}
},
ORACLE11G {
@Override
DatabaseAccess getImpl() {
return new Oracle11GAccess();
}
@Override
public void use() {
ConfigurationDB.useOracle11gDB();
}
};
public String getKey() {
return name().toLowerCase();
}
public static DatabaseType from(String dbType) {
if (dbType == null) {
throw new IllegalArgumentException("Unknown database type: null");
}
String normalized = dbType.trim().toLowerCase();
switch (normalized) {
case "postgresql":
case "pgsql":
case "pg":
return POSTGRES;
case "mssqlserver":
case "mssql":
return MSSQL;
case "mysqlserver":
case "mysql":
return MYSQL;
default:
return DatabaseType.valueOf(normalized.toUpperCase());
}
}
public InputStream getBinaryStream(ResultSet rs, int columnIndex) throws SQLException {
return rs.getBinaryStream(columnIndex);
}
public InputStream getBinaryStream(ResultSet rs, String columnLabel) throws SQLException {
return rs.getBinaryStream(columnLabel);
}
public int getBinarySqlType() {
return Types.BLOB;
}
abstract DatabaseAccess getImpl();
public abstract void use();
}
then:
public void setDatabaseType(String database) {
DatabaseType databaseType = DatabaseType.from(database);
databaseType.use();
}
|
|
||
| List<PointValue> getLatestPointValues(int dataPointId, int limit); | ||
|
|
||
| List<PointValue> getLatestPointValues(int dataPointId, int limit, long before); |
There was a problem hiding this comment.
What's the purpose of expanding this interface with new methods? There are already a lot of changes; here, we need to limit them to only the essentials.
| package org.scada_lts.dao; | ||
|
|
||
| public interface ISystemSettingsDAO { | ||
| public abstract void setValue(java.lang.String arg0, java.lang.String arg1); |
There was a problem hiding this comment.
Where are the variable names in these methods? This is an important element of information about how to use this method, what to do, and what to pass.
| default void init() {} | ||
|
|
||
| View findByName(String name); | ||
| List<View> findAll(); |
There was a problem hiding this comment.
We should not add new methods in this task.
| LOG.trace("insertPointLink(PointLinkVO pointLink) pointLink:" + pointLink.toString()); | ||
| } | ||
|
|
||
| KeyHolder keyHolder = new GeneratedKeyHolder(); |
There was a problem hiding this comment.
Is this implementation not working?
| @@ -1,273 +1,367 @@ | |||
| { | |||
| "name": "scada-lts-webapp", | |||
There was a problem hiding this comment.
You need to revert WebContent/resources/package-lock.json
| <bean id="dataSourceDAO" class="org.scada_lts.dao.DataSourceDAO"/> | ||
| <bean id="userDAO" class="org.scada_lts.dao.UserDAO"/> | ||
| <bean id="dataPointUserDAO-mysql" class="org.scada_lts.dao.DataPointUserDAO"/> | ||
| <bean id="dataPointUserDAO" class="org.scada_lts.factory.DatabaseAwareBeanFactory"> |
There was a problem hiding this comment.
Instead of using this class, the logic for getting the prefix etc. can be included, for example, in the ApplicationBeans class.
instead:
<bean id="dataPointUserDAO-mysql" class="org.scada_lts.dao.DataPointUserDAO"/>
<bean id="dataPointUserDAO" class="org.scada_lts.factory.DatabaseAwareBeanFactory">
<property name="prefix" value="dataPointUserDAO"/>
</bean>
we only use:
<bean id="dataPointUserDAO-mysql" class="org.scada_lts.dao.DataPointUserDAO"/>
then in ApplicationBeans:
public static IDataPointUserDAO getDataPointUserDaoBean() {
return getBean("dataPointUserDAO-"+resolveDbKey(), IDataPointUserDAO.class);
}
edit: Okay, that's not enough, you would also need to load this context, just like other contexts, and include it in the contexts from ApplicationBeans, without any complicated logic.
Patch with corrections to your approach:
modular_refactor.patch
For this to work, you need to add the bean to the XML context with a specific database:
<bean id="getDatabaseBeans" class="org.scada_lts.web.beans.GetDatabaseBeans"/>
But i am suggest:
<bean id="dataPointUserDAO" class="${scadalts.database.dao.dataPointUserDao}" />
and in env.properties add:
scadalts.database.dao.dataPointUserDao=org.scada_lts.dao.DataPointUserDAO
We avoid complex logic, manual synchronization, loading additional contexts etc., spring takes care of everything, and to add a new library, just include the lib and indicate the path to the dao in env.properties and that's it.
| ':scadalts-ui:copyDataSourceJsSourceMap', | ||
| ':scadalts-ui:copyModernWatchListJsSourceMap', | ||
| ':scadalts-ui:copyPointHierarchyJsSourceMap', | ||
| ':scadalts-ui:copyViewsJsSourceMap' |
There was a problem hiding this comment.
What's the rationale? Did it replace the cascading of these tasks?
| boolean hasTypeOrSettableFilter = | ||
| (searchDataPointJson.getDataTypes() != null && !searchDataPointJson.getDataTypes().isEmpty()) | ||
| || (searchDataPointJson.getSettable() != null); | ||
| if(!hasTypeOrSettableFilter) { |
There was a problem hiding this comment.
Just restore this method to its previous state. What's the point of doing this in the code? What about double negations?
| * @author grzegorz bylica Abil'I.T. development team, sdt@abilit.eu | ||
| * | ||
| */ | ||
| @Service |
There was a problem hiding this comment.
This annotation needs to come back:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'viewHierarchyAPI' defined in file [/Users/kamiljarmusik/Documents/dev/apache-tomcat-9.0.75/webapps/Scada-LTS/WEB-INF/classes/org/scada_lts/web/mvc/api/ViewHierarchyAPI.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.scada_lts.service.ViewHierarchyService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
…9_Refactor_DAO_layer_for_modular_architecture
| } | ||
|
|
||
| @Override | ||
| public View findByName(String name) { |
There was a problem hiding this comment.
Why was the @Override annotation removed?
- Corrected PointEventDetectorDaoWithCache; - Added utils classes: CacheUtils, ObjectsPaginationUtils;