Skip to content

#3249 Refactor DAO layer for modular architecture#3252

Open
Patrykb0802 wants to merge 8 commits into
release/2.8.1from
feature/#3249_Refactor_DAO_layer_for_modular_architecture
Open

#3249 Refactor DAO layer for modular architecture#3252
Patrykb0802 wants to merge 8 commits into
release/2.8.1from
feature/#3249_Refactor_DAO_layer_for_modular_architecture

Conversation

@Patrykb0802
Copy link
Copy Markdown
Contributor

  • 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
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 12, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fdd37612-66e1-4d1a-9e3e-f6d5456f62ee

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/#3249_Refactor_DAO_layer_for_modular_architecture

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 12, 2026

Java Script Mocha Unit Test Results

268 tests  ±0   268 ✅ ±0   3s ⏱️ ±0s
 70 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌ ±0 

Results for commit 678ee67. ± Comparison against base commit 015f144.

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 12, 2026

Java JUnit Test Results

3 843 tests  ±0   3 843 ✅ ±0   55s ⏱️ -2s
  135 suites ±0       0 💤 ±0 
  135 files   ±0       0 ❌ ±0 

Results for commit 678ee67. ± Comparison against base commit 015f144.

♻️ This comment has been updated with latest results.

@Patrykb0802
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 24, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

else{
StreamUtils.transfer(rs.getBlob(fieldName).getBinaryStream(),out);
}
StreamUtils.transfer(ApplicationBeans.getBinaryDataHandler().getBinaryStream(rs, fieldName), out);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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() {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getSystemSettingsDAOBean -> getSystemSettingsDaoBean (camel)

}

abstract public DatabaseType getType();
abstract public String getTypeKey();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getDatabaseSize();

DataPointService dataPointService = new DataPointService();
DataPointVO dataPoint = dataPointService.getDataPoint(dataPointId);
DwrResponseI18n response = super.toggleDataPoint(dataPoint);
DwrResponseI18n response = super.toggleDataPointInternal(dataPoint);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was the name changed?

ViewHierarchyService service = ApplicationBeans.getViewHierarchyServiceBean();

cache = service.getAll();
cache = new ViewHierarchyService().getAll();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ViewHierarchyService service = new ViewHierarchyService();

cache = service.getAll();

}

@Override
public List<PointEventDetectorVO> getPointEventDetectors(long limit, int offset) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/org/scada_lts/dao/IViewDAO.java Outdated
default void init() {}

View findByName(String name);
List<View> findAll();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not add new methods in this task.

LOG.trace("insertPointLink(PointLinkVO pointLink) pointLink:" + pointLink.toString());
}

KeyHolder keyHolder = new GeneratedKeyHolder();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this implementation not working?

@@ -1,273 +1,367 @@
{
"name": "scada-lts-webapp",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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">
Copy link
Copy Markdown
Collaborator

@Limraj Limraj Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread build.gradle Outdated
':scadalts-ui:copyDataSourceJsSourceMap',
':scadalts-ui:copyModernWatchListJsSourceMap',
':scadalts-ui:copyPointHierarchyJsSourceMap',
':scadalts-ui:copyViewsJsSourceMap'
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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: {}

}

@Override
public View findByName(String name) {
Copy link
Copy Markdown
Collaborator

@Limraj Limraj Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was the @Override annotation removed?

Limraj and others added 2 commits April 14, 2026 16:48
- Corrected PointEventDetectorDaoWithCache;
- Added utils classes: CacheUtils, ObjectsPaginationUtils;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants