This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch 3_0_X
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/3_0_X by this push:
     new 78e28a7ddc [SYNCOPE-1662] Introducing JSON support for MariaDB (#797)
78e28a7ddc is described below

commit 78e28a7ddcc4126352363f2d02a1e39d6d6cabaa
Author: Francesco Chicchiriccò <ilgro...@users.noreply.github.com>
AuthorDate: Fri Jul 26 13:09:45 2024 +0200

    [SYNCOPE-1662] Introducing JSON support for MariaDB (#797)
---
 .github/workflows/mariadb.yml                      |  28 +++
 core/persistence-jpa-json/pom.xml                  | 116 ++++++++-
 .../jpa/MaJPAJSONPersistenceContext.java           | 100 ++++++++
 .../persistence/jpa/dao/AbstractJPAJSONAnyDAO.java |  89 +++----
 .../core/persistence/jpa/dao/MaJPAJSONAnyDAO.java  |  80 +++++++
 .../persistence/jpa/dao/MaJPAJSONAnySearchDAO.java | 138 +++++++++++
 .../persistence/jpa/dao/MaJPAJSONAuditConfDAO.java |  62 +++++
 .../jpa/dao/MaJPAJSONPlainSchemaDAO.java           |  46 ++++
 .../core/persistence/jpa/dao/MyJPAJSONAnyDAO.java  |   9 +-
 .../core/persistence/jpa/dao/PGJPAJSONAnyDAO.java  |   9 +-
 .../jpa/entity/MaJPAJSONEntityFactory.java         |  30 +++
 .../main/resources/META-INF/spring-orm-majson.xml  | 137 +++++++++++
 .../src/main/resources/META-INF/spring.factories   |   1 +
 .../src/main/resources/audit/audit_majson.sql      |  24 ++
 .../src/main/resources/core-majson.properties      |  33 +++
 .../src/main/resources/majson/indexes.xml          |  63 +++++
 .../src/main/resources/majson/views.xml            | 262 +++++++++++++++++++++
 .../jpa/JPAJSONTestContextCustomizer.java          |   7 +
 .../resources/core-majson-test.properties}         |  17 +-
 .../core/persistence/jpa/dao/AbstractAnyDAO.java   |   7 +-
 .../core/persistence/jpa/dao/JPAAnySearchDAO.java  |  22 +-
 .../core/persistence/jpa/inner/UserTest.java       |   4 +-
 .../core/src/main/resources/core-majson.properties |  20 +-
 ...mpose-mariadb.yml => docker-compose-majson.yml} |   9 +-
 .../docker-compose/docker-compose-mariadb.yml      |   1 +
 fit/core-reference/pom.xml                         | 117 +++++++++
 .../src/main/resources/core-majson.properties      |  35 +++
 .../reference-guide/configuration/dbms.adoc        |  67 ++++++
 src/site/xdoc/building.xml                         |   2 +-
 29 files changed, 1446 insertions(+), 89 deletions(-)

diff --git a/.github/workflows/mariadb.yml b/.github/workflows/mariadb.yml
index 2a1066eb66..536b5b7291 100644
--- a/.github/workflows/mariadb.yml
+++ b/.github/workflows/mariadb.yml
@@ -51,3 +51,31 @@ jobs:
       run: mvn -U -T 1C -P 'skipTests,all'
     - name: 'Integration Tests: MariaDB'
       run: mvn -f fit/core-reference/pom.xml -P mariadb-it 
-Dinvoker.streamLogs=true -Dmodernizer.skip=true -Drat.skip=true 
-Dcheckstyle.skip=true -Djacoco.skip=true
+
+  majson:
+    runs-on: ubuntu-latest
+
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v4
+    - name: Setup Java JDK
+      uses: actions/setup-java@v4
+      with:
+        distribution: 'zulu'
+        java-version: 11
+    - name: Setup Maven
+      uses: stCarolas/setup-maven@v5
+      with:
+        maven-version: 3.9.6
+    - uses: actions/cache@v4
+      with:
+        path: ~/.m2/repository
+        key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+        restore-keys: |
+          ${{ runner.os }}-maven-
+    - name: Build
+      run: mvn -U -T 1C -P 'skipTests,all'
+    - name: 'Unit Tests: MariaDB JPA JSON'
+      run: mvn -f core/persistence-jpa-json/pom.xml -P majson 
-Dinvoker.streamLogs=true -Dmodernizer.skip=true -Dianal.phase=none 
-Drat.skip=true -Dcheckstyle.skip=true -Djacoco.skip=true
+    - name: 'Integration Tests: MariaDB JPA JSON'
+      run: mvn -f fit/core-reference/pom.xml -P majson-it 
-Dinvoker.streamLogs=true -Dmodernizer.skip=true -Drat.skip=true 
-Dcheckstyle.skip=true -Djacoco.skip=true
diff --git a/core/persistence-jpa-json/pom.xml 
b/core/persistence-jpa-json/pom.xml
index c06b335993..50c0ee5981 100644
--- a/core/persistence-jpa-json/pom.xml
+++ b/core/persistence-jpa-json/pom.xml
@@ -165,7 +165,7 @@ under the License.
         <!-- possible values: sql | schema -->
         <action>sql</action>
 
-        <!-- possible values: pgjsonb | myjson | ojson -->
+        <!-- possible values: pgjsonb | myjson | majson | ojson -->
         <orm>ojson</orm>
 
         <skipTests>true</skipTests>
@@ -422,6 +422,120 @@ under the License.
       </build>
     </profile>
 
+    <profile>
+      <id>majson</id>
+
+      <dependencies>
+        <dependency>
+          <groupId>org.mariadb.jdbc</groupId>
+          <artifactId>mariadb-java-client</artifactId>
+          <version>${jdbc.mariadb.version}</version>
+          <scope>test</scope>
+        </dependency>
+      </dependencies>
+
+      <build>
+        <defaultGoal>clean verify</defaultGoal>
+
+        <plugins>
+          <plugin>
+            <groupId>org.codehaus.mojo</groupId>
+            <artifactId>build-helper-maven-plugin</artifactId>
+            <executions>
+              <execution>
+                <id>add-test-source</id>
+                <phase>generate-test-sources</phase>
+                <goals>
+                  <goal>add-test-source</goal>
+                </goals>
+                <configuration>
+                  <sources>
+                    
<source>${basedir}/../persistence-jpa/src/test/java</source>
+                  </sources>
+                </configuration>
+              </execution>
+            </executions>
+          </plugin>
+      
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-surefire-plugin</artifactId>
+            <configuration>
+              <skip>true</skip>
+            </configuration>
+          </plugin>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-failsafe-plugin</artifactId>
+            <configuration>
+              <includes>
+                <include>**/*Test.java</include>
+              </includes>
+              <excludedGroups>multitenancy,plainAttrTable</excludedGroups>
+              <systemPropertyVariables>
+                <profileId>${project.activeProfiles[0].id}</profileId>
+                
<CORE_PROPERTIES>classpath:core-majson.properties,classpath:core-majson-test.properties</CORE_PROPERTIES>
+                
<DB_CONTAINER_IP>${docker.container.mariadb.ip}</DB_CONTAINER_IP>
+                
<syncope.connid.location>file:${bundles.directory}/</syncope.connid.location>
+              </systemPropertyVariables>
+            </configuration>
+          </plugin>
+
+          <plugin>
+            <groupId>io.fabric8</groupId>
+            <artifactId>docker-maven-plugin</artifactId>
+            <configuration>
+              <images>
+                <image>
+                  <alias>mariadb</alias>
+                  <name>mariadb:${docker.mariadb.version}</name>
+                  <run>
+                    <env>
+                      <MYSQL_ROOT_PASSWORD>password</MYSQL_ROOT_PASSWORD>
+                      <MYSQL_DATABASE>syncope</MYSQL_DATABASE>
+                      <MYSQL_USER>syncope</MYSQL_USER>
+                      <MYSQL_PASSWORD>syncope</MYSQL_PASSWORD>
+                    </env>
+                    <tmpfs>
+                      <mount>/var/lib/mysql:rw</mount>
+                    </tmpfs>
+                    <wait>
+                      <log>MariaDB init process done. Ready for start up.</log>
+                      <time>30000</time>
+                    </wait>
+                  </run>
+                </image>
+              </images>
+            </configuration>
+            <executions>
+              <execution>
+                <id>start-mariadb</id>
+                <phase>pre-integration-test</phase>
+                <goals>
+                  <goal>start</goal>
+                </goals>
+              </execution>
+              <execution>
+                <id>stop-mariadb</id>
+                <phase>post-integration-test</phase>
+                <goals>
+                  <goal>stop</goal>
+                  <goal>remove</goal>
+                </goals>
+              </execution>
+            </executions>
+          </plugin>
+        </plugins>
+        
+        <testResources>
+          <testResource>
+            <directory>src/test/resources</directory>
+            <filtering>true</filtering>
+          </testResource>
+        </testResources>
+      </build>
+    </profile>
+
     <profile>
       <id>ojson</id>
 
diff --git 
a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/MaJPAJSONPersistenceContext.java
 
b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/MaJPAJSONPersistenceContext.java
new file mode 100644
index 0000000000..a35d2a289f
--- /dev/null
+++ 
b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/MaJPAJSONPersistenceContext.java
@@ -0,0 +1,100 @@
+/*
+ * 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 org.apache.syncope.core.persistence.jpa;
+
+import 
org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
+import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
+import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
+import org.apache.syncope.core.persistence.api.dao.AuditConfDAO;
+import org.apache.syncope.core.persistence.api.dao.DynRealmDAO;
+import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.core.persistence.api.dao.GroupDAO;
+import org.apache.syncope.core.persistence.api.dao.JPAJSONAnyDAO;
+import org.apache.syncope.core.persistence.api.dao.PlainAttrDAO;
+import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.core.persistence.api.dao.RealmDAO;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.jpa.dao.MaJPAJSONAnyDAO;
+import org.apache.syncope.core.persistence.jpa.dao.MaJPAJSONAnySearchDAO;
+import org.apache.syncope.core.persistence.jpa.dao.MaJPAJSONAuditConfDAO;
+import org.apache.syncope.core.persistence.jpa.dao.MaJPAJSONPlainSchemaDAO;
+import org.apache.syncope.core.persistence.jpa.entity.MaJPAJSONEntityFactory;
+import 
org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import 
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Lazy;
+
+@ConditionalOnExpression("#{'${provisioning.quartz.sql}' matches 
'.*mariadb.*'}")
+public class MaJPAJSONPersistenceContext extends JPAJSONPersistenceContext {
+
+    @ConditionalOnMissingBean(name = "maJPAJSONEntityFactory")
+    @Bean
+    public EntityFactory entityFactory() {
+        return new MaJPAJSONEntityFactory();
+    }
+
+    @ConditionalOnMissingBean(name = "maJPAJSONAnyDAO")
+    @Bean
+    public JPAJSONAnyDAO anyDAO(final @Lazy PlainSchemaDAO plainSchemaDAO) {
+        return new MaJPAJSONAnyDAO(plainSchemaDAO);
+    }
+
+    @ConditionalOnMissingBean(name = "maJPAJSONAnySearchDAO")
+    @Bean
+    public AnySearchDAO anySearchDAO(
+            final @Lazy RealmDAO realmDAO,
+            final @Lazy DynRealmDAO dynRealmDAO,
+            final @Lazy UserDAO userDAO,
+            final @Lazy GroupDAO groupDAO,
+            final @Lazy AnyObjectDAO anyObjectDAO,
+            final @Lazy PlainSchemaDAO schemaDAO,
+            final @Lazy EntityFactory entityFactory,
+            final AnyUtilsFactory anyUtilsFactory,
+            final PlainAttrValidationManager validator) {
+
+        return new MaJPAJSONAnySearchDAO(
+                realmDAO,
+                dynRealmDAO,
+                userDAO,
+                groupDAO,
+                anyObjectDAO,
+                schemaDAO,
+                entityFactory,
+                anyUtilsFactory,
+                validator);
+    }
+
+    @ConditionalOnMissingBean(name = "maJPAJSONAuditConfDAO")
+    @Bean
+    public AuditConfDAO auditConfDAO() {
+        return new MaJPAJSONAuditConfDAO();
+    }
+
+    @ConditionalOnMissingBean(name = "maJPAJSONPlainSchemaDAO")
+    @Bean
+    public PlainSchemaDAO plainSchemaDAO(
+            final AnyUtilsFactory anyUtilsFactory,
+            final @Lazy PlainAttrDAO plainAttrDAO,
+            final @Lazy ExternalResourceDAO resourceDAO) {
+
+        return new MaJPAJSONPlainSchemaDAO(anyUtilsFactory, plainAttrDAO, 
resourceDAO);
+    }
+}
diff --git 
a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractJPAJSONAnyDAO.java
 
b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractJPAJSONAnyDAO.java
index 1101148d7c..c06f89ca11 100644
--- 
a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractJPAJSONAnyDAO.java
+++ 
b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractJPAJSONAnyDAO.java
@@ -53,6 +53,29 @@ import 
org.springframework.transaction.annotation.Transactional;
 
 abstract class AbstractJPAJSONAnyDAO extends AbstractDAO<AbstractEntity> 
implements JPAJSONAnyDAO {
 
+    /**
+     * Split an attribute value recurring on provided literals/tokens.
+     *
+     * @param attrValue value to be split
+     * @param literals literals/tokens
+     * @return split value
+     */
+    private static List<String> split(final String attrValue, final 
List<String> literals) {
+        List<String> attrValues = new ArrayList<>();
+
+        if (literals.isEmpty()) {
+            attrValues.add(attrValue);
+        } else {
+            for (String token : 
attrValue.split(Pattern.quote(literals.get(0)))) {
+                if (!token.isEmpty()) {
+                    attrValues.addAll(split(token, literals.subList(1, 
literals.size())));
+                }
+            }
+        }
+
+        return attrValues;
+    }
+
     protected final PlainSchemaDAO plainSchemaDAO;
 
     protected AbstractJPAJSONAnyDAO(final PlainSchemaDAO plainSchemaDAO) {
@@ -129,6 +152,20 @@ abstract class AbstractJPAJSONAnyDAO extends 
AbstractDAO<AbstractEntity> impleme
         return result;
     }
 
+    protected String plainAttrQuery(
+            final String table,
+            final AnyUtils anyUtils,
+            final PlainSchema schema,
+            final PlainAttrValue attrValue,
+            final boolean ignoreCaseMatch,
+            final List<Object> queryParams) {
+
+        queryParams.add(schema.getKey());
+        queryParams.add(getAttrValue(schema, attrValue, ignoreCaseMatch));
+
+        return queryBegin(table) + "WHERE " + attrValueMatch(anyUtils, schema, 
attrValue, ignoreCaseMatch);
+    }
+
     @SuppressWarnings("unchecked")
     @Transactional(readOnly = true)
     @Override
@@ -144,11 +181,12 @@ abstract class AbstractJPAJSONAnyDAO extends 
AbstractDAO<AbstractEntity> impleme
             return List.of();
         }
 
+        List<Object> queryParams = new ArrayList<>();
         Query query = entityManager().createNativeQuery(
-                queryBegin(table)
-                + "WHERE " + attrValueMatch(anyUtils, schema, attrValue, 
ignoreCaseMatch));
-        query.setParameter(1, schema.getKey());
-        query.setParameter(2, getAttrValue(schema, attrValue, 
ignoreCaseMatch));
+                plainAttrQuery(table, anyUtils, schema, attrValue, 
ignoreCaseMatch, queryParams));
+        for (int i = 0; i < queryParams.size(); i++) {
+            query.setParameter(i + 1, queryParams.get(i));
+        }
 
         return buildResult(anyUtils, query.getResultList());
     }
@@ -177,31 +215,8 @@ abstract class AbstractJPAJSONAnyDAO extends 
AbstractDAO<AbstractEntity> impleme
                 : Optional.of(result.get(0));
     }
 
-    /**
-     * Split an attribute value recurring on provided literals/tokens.
-     *
-     * @param attrValue value to be split
-     * @param literals literals/tokens
-     * @return split value
-     */
-    protected List<String> split(final String attrValue, final List<String> 
literals) {
-        List<String> attrValues = new ArrayList<>();
-
-        if (literals.isEmpty()) {
-            attrValues.add(attrValue);
-        } else {
-            for (String token : 
attrValue.split(Pattern.quote(literals.get(0)))) {
-                if (!token.isEmpty()) {
-                    attrValues.addAll(split(token, literals.subList(1, 
literals.size())));
-                }
-            }
-        }
-
-        return attrValues;
-    }
-
     @SuppressWarnings("unchecked")
-    protected List<Object> findByDerAttrValue(
+    private List<Object> findByDerAttrValue(
             final String table,
             final Map<String, List<Object>> clauses) {
 
@@ -302,26 +317,18 @@ abstract class AbstractJPAJSONAnyDAO extends 
AbstractDAO<AbstractEntity> impleme
                     // clear builder
                     bld.delete(0, bld.length());
 
-                    PlainAttrValue attrValue;
-                    if (schema.isUniqueConstraint()) {
-                        attrValue = anyUtils.newPlainAttrUniqueValue();
-                    } else {
-                        attrValue = anyUtils.newPlainAttrValue();
-                    }
+                    PlainAttrValue attrValue = schema.isUniqueConstraint()
+                            ? anyUtils.newPlainAttrUniqueValue()
+                            : anyUtils.newPlainAttrValue();
                     attrValue.setStringValue(attrValues.get(i));
 
+                    List<Object> queryParams = new ArrayList<>();
                     bld.append('(').
-                            append(queryBegin(table)).
-                            append("WHERE ").
-                            append(attrValueMatch(anyUtils, schema, attrValue, 
ignoreCaseMatch)).
+                            append(plainAttrQuery(table, anyUtils, schema, 
attrValue, ignoreCaseMatch, queryParams)).
                             append(')');
 
                     used.add(identifiers.get(i));
 
-                    List<Object> queryParams = new ArrayList<>();
-                    queryParams.add(schema.getKey());
-                    queryParams.add(getAttrValue(schema, attrValue, 
ignoreCaseMatch));
-
                     clauses.put(bld.toString(), queryParams);
                 }
             }
diff --git 
a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONAnyDAO.java
 
b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONAnyDAO.java
new file mode 100644
index 0000000000..72a5466869
--- /dev/null
+++ 
b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONAnyDAO.java
@@ -0,0 +1,80 @@
+/*
+ * 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 org.apache.syncope.core.persistence.jpa.dao;
+
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.Optional;
+import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.core.persistence.api.entity.AnyUtils;
+import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.core.persistence.api.entity.PlainSchema;
+
+public class MaJPAJSONAnyDAO extends AbstractJPAJSONAnyDAO {
+
+    public MaJPAJSONAnyDAO(final PlainSchemaDAO plainSchemaDAO) {
+        super(plainSchemaDAO);
+    }
+
+    @Override
+    protected String queryBegin(final String table) {
+        throw new UnsupportedOperationException("This method shall never be 
called");
+    }
+
+    @Override
+    protected String attrValueMatch(
+            final AnyUtils anyUtils,
+            final PlainSchema schema,
+            final PlainAttrValue attrValue,
+            final boolean ignoreCaseMatch) {
+
+        throw new UnsupportedOperationException("This method shall never be 
called");
+    }
+
+    @Override
+    protected String plainAttrQuery(
+            final String table,
+            final AnyUtils anyUtils,
+            final PlainSchema schema,
+            final PlainAttrValue attrValue,
+            final boolean ignoreCaseMatch,
+            final List<Object> queryParams) {
+
+        queryParams.add(schema.getKey());
+        queryParams.add(attrValue.getStringValue());
+        queryParams.add(attrValue.getBooleanValue());
+        queryParams.add(Optional.ofNullable(attrValue.getDateValue()).
+                
map(DateTimeFormatter.ISO_OFFSET_DATE_TIME::format).orElse(null));
+        queryParams.add(attrValue.getLongValue());
+        queryParams.add(attrValue.getDoubleValue());
+
+        SearchViewSupport svs = new SearchViewSupport(anyUtils.anyTypeKind());
+        return "SELECT DISTINCT any_id FROM "
+                + (schema.isUniqueConstraint() ? svs.uniqueAttr().name : 
svs.attr().name)
+                + " WHERE schema_id = ? AND ((stringValue IS NOT NULL"
+                + " AND "
+                + (ignoreCaseMatch ? "LOWER(" : "BINARY ") + "stringValue" + 
(ignoreCaseMatch ? ")" : "")
+                + " = "
+                + (ignoreCaseMatch ? "LOWER(" : "") + "?" + (ignoreCaseMatch ? 
")" : "") + ')'
+                + " OR (booleanValue IS NOT NULL AND booleanValue = ?)"
+                + " OR (dateValue IS NOT NULL AND dateValue = ?)"
+                + " OR (longValue IS NOT NULL AND longValue = ?)"
+                + " OR (doubleValue IS NOT NULL AND doubleValue = ?))";
+    }
+}
diff --git 
a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONAnySearchDAO.java
 
b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONAnySearchDAO.java
new file mode 100644
index 0000000000..8ec7e90070
--- /dev/null
+++ 
b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONAnySearchDAO.java
@@ -0,0 +1,138 @@
+/*
+ * 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 org.apache.syncope.core.persistence.jpa.dao;
+
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.Optional;
+import org.apache.commons.lang3.tuple.Pair;
+import 
org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
+import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
+import org.apache.syncope.core.persistence.api.dao.DynRealmDAO;
+import org.apache.syncope.core.persistence.api.dao.GroupDAO;
+import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.core.persistence.api.dao.RealmDAO;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.dao.search.AnyCond;
+import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
+import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.entity.JSONPlainAttr;
+import org.apache.syncope.core.persistence.api.entity.PlainAttr;
+import org.apache.syncope.core.persistence.api.entity.PlainAttrUniqueValue;
+import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.core.persistence.api.entity.PlainSchema;
+import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
+
+public class MaJPAJSONAnySearchDAO extends JPAAnySearchDAO {
+
+    public MaJPAJSONAnySearchDAO(
+            final RealmDAO realmDAO,
+            final DynRealmDAO dynRealmDAO,
+            final UserDAO userDAO,
+            final GroupDAO groupDAO,
+            final AnyObjectDAO anyObjectDAO,
+            final PlainSchemaDAO schemaDAO,
+            final EntityFactory entityFactory,
+            final AnyUtilsFactory anyUtilsFactory,
+            final PlainAttrValidationManager validator) {
+
+        super(
+                realmDAO,
+                dynRealmDAO,
+                userDAO,
+                groupDAO,
+                anyObjectDAO,
+                schemaDAO,
+                entityFactory,
+                anyUtilsFactory,
+                validator);
+    }
+
+    @Override
+    protected String getQuery(
+            final AttrCond cond,
+            final boolean not,
+            final List<Object> parameters,
+            final SearchSupport svs) {
+
+        Pair<PlainSchema, PlainAttrValue> checked = check(cond, 
svs.anyTypeKind);
+
+        // normalize NULL / NOT NULL checks
+        if (not) {
+            if (cond.getType() == AttrCond.Type.ISNULL) {
+                cond.setType(AttrCond.Type.ISNOTNULL);
+            } else if (cond.getType() == AttrCond.Type.ISNOTNULL) {
+                cond.setType(AttrCond.Type.ISNULL);
+            }
+        }
+
+        StringBuilder query =
+                new StringBuilder("SELECT DISTINCT any_id FROM 
").append(svs.field().name).append(" WHERE ");
+        switch (cond.getType()) {
+            case ISNOTNULL:
+                query.append("JSON_SEARCH(plainAttrs, 'one', '").
+                        append(checked.getLeft().getKey()).
+                        append("', NULL, '$[*].schema') IS NOT NULL");
+                break;
+
+            case ISNULL:
+                query.append("JSON_SEARCH(plainAttrs, 'one', '").
+                        append(checked.getLeft().getKey()).
+                        append("', NULL, '$[*].schema') IS NULL");
+                break;
+
+            default:
+                if (!not && cond.getType() == AttrCond.Type.EQ) {
+                    PlainAttr<?> container = 
anyUtilsFactory.getInstance(svs.anyTypeKind).newPlainAttr();
+                    container.setSchema(checked.getLeft());
+                    if (checked.getRight() instanceof PlainAttrUniqueValue) {
+                        container.setUniqueValue((PlainAttrUniqueValue) 
checked.getRight());
+                    } else {
+                        ((JSONPlainAttr) container).add(checked.getRight());
+                    }
+
+                    query.append("JSON_CONTAINS(plainAttrs, '").
+                            
append(POJOHelper.serialize(List.of(container)).replace("'", "''")).
+                            append("')");
+                } else {
+                    query = new StringBuilder("SELECT DISTINCT any_id FROM ");
+                    if (not && !(cond instanceof AnyCond) && 
checked.getLeft().isMultivalue()) {
+                        query.append(svs.field().name).append(" WHERE ");
+                    } else {
+                        query.append((checked.getLeft().isUniqueConstraint()
+                                ? svs.asSearchViewSupport().uniqueAttr().name
+                                : svs.asSearchViewSupport().attr().name)).
+                                append(" WHERE 
schema_id='").append(checked.getLeft().getKey());
+                    }
+
+                    Optional.ofNullable(checked.getRight().getDateValue()).
+                            
map(DateTimeFormatter.ISO_OFFSET_DATE_TIME::format).
+                            ifPresent(formatted -> {
+                                checked.getRight().setDateValue(null);
+                                checked.getRight().setStringValue(formatted);
+                            });
+
+                    fillAttrQuery(query, checked.getRight(), 
checked.getLeft(), cond, not, parameters, svs);
+                }
+        }
+
+        return query.toString();
+    }
+}
diff --git 
a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONAuditConfDAO.java
 
b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONAuditConfDAO.java
new file mode 100644
index 0000000000..dbc1bd7423
--- /dev/null
+++ 
b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONAuditConfDAO.java
@@ -0,0 +1,62 @@
+/*
+ * 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 org.apache.syncope.core.persistence.jpa.dao;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
+
+public class MaJPAJSONAuditConfDAO extends AbstractJPAJSONLoggerDAO {
+
+    protected static class MaMessageCriteriaBuilder extends 
JSONMessageCriteriaBuilder {
+
+        @Override
+        protected String doBuild(final List<ObjectNode> containers) {
+            if (entityKey != null) {
+                query.append(andIfNeeded()).append('(').
+                        
append("JSON_VALUE(").append(AUDIT_ENTRY_MESSAGE_COLUMN).append(", '$.before') 
LIKE '%").
+                        append(entityKey).
+                        append("%' OR ").
+                        
append("JSON_VALUE(").append(AUDIT_ENTRY_MESSAGE_COLUMN).append(", '$.input') 
LIKE '%").
+                        append(entityKey).
+                        append("%' OR ").
+                        
append("JSON_VALUE(").append(AUDIT_ENTRY_MESSAGE_COLUMN).append(", '$.output') 
LIKE '%").
+                        append(entityKey).
+                        append("%')");
+            }
+
+            if (!containers.isEmpty()) {
+                query.append(andIfNeeded()).append('(').
+                        append(containers.stream().
+                                map(container -> "JSON_CONTAINS(" + 
AUDIT_ENTRY_MESSAGE_COLUMN + ", '"
+                                + POJOHelper.serialize(container).replace("'", 
"''")
+                                + "')").collect(Collectors.joining(" OR "))).
+                        append(')');
+            }
+
+            return query.toString();
+        }
+    }
+
+    @Override
+    protected MessageCriteriaBuilder messageCriteriaBuilder(final String 
entityKey) {
+        return new MaMessageCriteriaBuilder().entityKey(entityKey);
+    }
+}
diff --git 
a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONPlainSchemaDAO.java
 
b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONPlainSchemaDAO.java
new file mode 100644
index 0000000000..0a1f5c157b
--- /dev/null
+++ 
b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MaJPAJSONPlainSchemaDAO.java
@@ -0,0 +1,46 @@
+/*
+ * 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 org.apache.syncope.core.persistence.jpa.dao;
+
+import javax.persistence.Query;
+import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.core.persistence.api.dao.PlainAttrDAO;
+import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
+import org.apache.syncope.core.persistence.api.entity.PlainAttr;
+import org.apache.syncope.core.persistence.api.entity.PlainSchema;
+
+public class MaJPAJSONPlainSchemaDAO extends AbstractJPAJSONPlainSchemaDAO {
+
+    public MaJPAJSONPlainSchemaDAO(
+            final AnyUtilsFactory anyUtilsFactory,
+            final PlainAttrDAO plainAttrDAO,
+            final ExternalResourceDAO resourceDAO) {
+
+        super(anyUtilsFactory, plainAttrDAO, resourceDAO);
+    }
+
+    @Override
+    public <T extends PlainAttr<?>> boolean hasAttrs(final PlainSchema schema, 
final Class<T> reference) {
+        Query query = entityManager().createNativeQuery(
+                "SELECT COUNT(id) FROM " + new 
SearchSupport(getAnyTypeKind(reference)).field().name
+                + " WHERE JSON_CONTAINS(plainAttrs, '[{\"schema\":\"" + 
schema.getKey() + "\"}]')");
+
+        return ((Number) query.getSingleResult()).intValue() > 0;
+    }
+}
diff --git 
a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MyJPAJSONAnyDAO.java
 
b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MyJPAJSONAnyDAO.java
index 06ad3692c1..ebae9b2d35 100644
--- 
a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MyJPAJSONAnyDAO.java
+++ 
b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MyJPAJSONAnyDAO.java
@@ -50,16 +50,11 @@ public class MyJPAJSONAnyDAO extends AbstractJPAJSONAnyDAO {
         Pair<String, Boolean> schemaInfo = schemaInfo(schema.getType(), 
ignoreCaseMatch);
         if (schemaInfo.getRight()) {
             return "plainSchema = ? "
-                    + "AND "
-                    + (schemaInfo.getRight() ? "LOWER(" : "")
+                    + "AND LOWER("
                     + (schema.isUniqueConstraint()
                     ? "attrUniqueValue ->> '$." + schemaInfo.getLeft() + '\''
                     : schemaInfo.getLeft())
-                    + (schemaInfo.getRight() ? ")" : "")
-                    + " = "
-                    + (schemaInfo.getRight() ? "LOWER(" : "")
-                    + '?'
-                    + (schemaInfo.getRight() ? ")" : "");
+                    + ") = LOWER(?)";
         } else {
             PlainAttr<?> container = anyUtils.newPlainAttr();
             container.setSchema(schema);
diff --git 
a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnyDAO.java
 
b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnyDAO.java
index 2bc9ad4e2a..ecce397ad8 100644
--- 
a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnyDAO.java
+++ 
b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnyDAO.java
@@ -52,15 +52,10 @@ public class PGJPAJSONAnyDAO extends AbstractJPAJSONAnyDAO {
         Pair<String, Boolean> schemaInfo = schemaInfo(schema.getType(), 
ignoreCaseMatch);
         if (schemaInfo.getRight()) {
             return "attrs ->> 'schema' = ? "
-                    + "AND "
-                    + (schemaInfo.getRight() ? "LOWER(" : "")
+                    + "AND LOWER("
                     + (schema.isUniqueConstraint() ? "attrs -> 'uniqueValue'" 
: "attrValues")
                     + " ->> '" + schemaInfo.getLeft()
-                    + '\'' + (schemaInfo.getRight() ? ")" : "")
-                    + " = "
-                    + (schemaInfo.getRight() ? "LOWER(" : "")
-                    + '?'
-                    + (schemaInfo.getRight() ? ")" : "");
+                    + "') = LOWER(?)";
         }
 
         PlainAttr<?> container = anyUtils.newPlainAttr();
diff --git 
a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/MaJPAJSONEntityFactory.java
 
b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/MaJPAJSONEntityFactory.java
new file mode 100644
index 0000000000..c509826cf3
--- /dev/null
+++ 
b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/entity/MaJPAJSONEntityFactory.java
@@ -0,0 +1,30 @@
+/*
+ * 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 org.apache.syncope.core.persistence.jpa.entity;
+
+import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
+import org.apache.syncope.core.persistence.jpa.dao.MaJPAJSONAnySearchDAO;
+
+public class MaJPAJSONEntityFactory extends JPAJSONEntityFactory {
+
+    @Override
+    public Class<? extends AnySearchDAO> anySearchDAOClass() {
+        return MaJPAJSONAnySearchDAO.class;
+    }
+}
diff --git 
a/core/persistence-jpa-json/src/main/resources/META-INF/spring-orm-majson.xml 
b/core/persistence-jpa-json/src/main/resources/META-INF/spring-orm-majson.xml
new file mode 100644
index 0000000000..ec73b4b348
--- /dev/null
+++ 
b/core/persistence-jpa-json/src/main/resources/META-INF/spring-orm-majson.xml
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm";
+                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+                 
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm 
+                                     
http://java.sun.com/xml/ns/persistence/orm_2_0.xsd";
+                 version="2.0">
+  
+  <persistence-unit-metadata>
+    <persistence-unit-defaults>
+      <entity-listeners>
+        <entity-listener 
class="org.apache.syncope.core.persistence.jpa.validation.entity.EntityValidationListener">
+          <pre-persist method-name="validate"/>
+          <pre-update method-name="validate"/>
+        </entity-listener>
+      </entity-listeners>
+    </persistence-unit-defaults>
+  </persistence-unit-metadata>
+ 
+  <entity 
class="org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAJSONAnyObject">
+    <attributes>
+      <basic name="plainAttrs">
+        <column column-definition="json"/>
+        <lob/>
+      </basic>
+    </attributes>
+  </entity>
+    
+  <entity 
class="org.apache.syncope.core.persistence.jpa.entity.group.JPAJSONGroup">
+    <attributes>
+      <basic name="plainAttrs">
+        <column column-definition="json"/>
+        <lob/>
+      </basic>
+    </attributes>
+  </entity>
+
+  <entity 
class="org.apache.syncope.core.persistence.jpa.entity.user.JPAJSONUser">
+    <attributes>
+      <basic name="plainAttrs">
+        <column column-definition="json"/>
+        <lob/>
+      </basic>
+    </attributes>
+  </entity>
+
+  <entity 
class="org.apache.syncope.core.persistence.jpa.entity.user.JPAJSONLinkedAccount">
+    <attributes>
+      <basic name="plainAttrs">
+        <column column-definition="json"/>
+        <lob/>
+      </basic>
+    </attributes>
+  </entity>
+
+  <entity 
class="org.apache.syncope.core.persistence.jpa.entity.group.JPAGroup">
+    <attributes>
+      <many-to-one name="userOwner" 
target-entity="org.apache.syncope.core.persistence.jpa.entity.user.JPAJSONUser"/>
+      <many-to-one name="groupOwner" 
target-entity="org.apache.syncope.core.persistence.jpa.entity.group.JPAJSONGroup"/>
+    </attributes>
+  </entity>
+
+  <entity 
class="org.apache.syncope.core.persistence.jpa.entity.group.JPATypeExtension">
+    <attributes>
+      <many-to-one name="group" 
target-entity="org.apache.syncope.core.persistence.jpa.entity.group.JPAJSONGroup"/>
+    </attributes>
+  </entity>
+
+  <entity 
class="org.apache.syncope.core.persistence.jpa.entity.user.JPAUMembership">
+    <attributes>
+      <many-to-one name="leftEnd" 
target-entity="org.apache.syncope.core.persistence.jpa.entity.user.JPAJSONUser">
+        <join-column name="user_id"/>
+      </many-to-one>
+      <many-to-one name="rightEnd" 
target-entity="org.apache.syncope.core.persistence.jpa.entity.group.JPAJSONGroup">
+        <join-column name="group_id"/>
+      </many-to-one>        
+    </attributes>
+  </entity>
+  <entity 
class="org.apache.syncope.core.persistence.jpa.entity.user.JPAUDynGroupMembership">
+    <attributes>
+      <one-to-one name="group" 
target-entity="org.apache.syncope.core.persistence.jpa.entity.group.JPAJSONGroup"/>
+    </attributes>
+  </entity>
+  <entity 
class="org.apache.syncope.core.persistence.jpa.entity.user.JPAURelationship">
+    <attributes>
+      <many-to-one name="leftEnd" 
target-entity="org.apache.syncope.core.persistence.jpa.entity.user.JPAJSONUser">
+        <join-column name="user_id"/>
+      </many-to-one>
+      <many-to-one name="rightEnd" 
target-entity="org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAJSONAnyObject">
+        <join-column name="anyObject_id"/>
+      </many-to-one>
+    </attributes>
+  </entity>
+
+  <entity 
class="org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAMembership">
+    <attributes>
+      <many-to-one name="leftEnd" 
target-entity="org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAJSONAnyObject">
+        <join-column name="anyObject_id"/>
+      </many-to-one>
+      <many-to-one name="rightEnd" 
target-entity="org.apache.syncope.core.persistence.jpa.entity.group.JPAJSONGroup">
+        <join-column name="group_id"/>
+      </many-to-one>
+    </attributes>
+  </entity>
+  <entity 
class="org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAADynGroupMembership">
+    <attributes>
+      <one-to-one name="group" 
target-entity="org.apache.syncope.core.persistence.jpa.entity.group.JPAJSONGroup"/>
+    </attributes>
+  </entity>
+  <entity 
class="org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAARelationship">
+    <attributes>
+      <many-to-one name="leftEnd" 
target-entity="org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAJSONAnyObject">
+        <join-column name="left_anyObject_id"/>
+      </many-to-one>
+      <many-to-one name="rightEnd" 
target-entity="org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAJSONAnyObject">
+        <join-column name="right_anyObject_id"/>
+      </many-to-one>
+    </attributes>
+  </entity>
+</entity-mappings>
diff --git 
a/core/persistence-jpa-json/src/main/resources/META-INF/spring.factories 
b/core/persistence-jpa-json/src/main/resources/META-INF/spring.factories
index 3eea832444..1176397e32 100644
--- a/core/persistence-jpa-json/src/main/resources/META-INF/spring.factories
+++ b/core/persistence-jpa-json/src/main/resources/META-INF/spring.factories
@@ -18,4 +18,5 @@
 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
   org.apache.syncope.core.persistence.jpa.PGJPAJSONPersistenceContext,\
   org.apache.syncope.core.persistence.jpa.MyJPAJSONPersistenceContext,\
+  org.apache.syncope.core.persistence.jpa.MaJPAJSONPersistenceContext,\
   org.apache.syncope.core.persistence.jpa.OJPAJSONPersistenceContext
diff --git 
a/core/persistence-jpa-json/src/main/resources/audit/audit_majson.sql 
b/core/persistence-jpa-json/src/main/resources/audit/audit_majson.sql
new file mode 100644
index 0000000000..13aae9f9a5
--- /dev/null
+++ b/core/persistence-jpa-json/src/main/resources/audit/audit_majson.sql
@@ -0,0 +1,24 @@
+-- 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.
+
+CREATE TABLE IF NOT EXISTS AuditEntry (
+  EVENT_DATE TIMESTAMP,
+  LOGGER_LEVEL VARCHAR(255) NOT NULL,
+  LOGGER VARCHAR(255) NOT NULL,
+  MESSAGE JSON NOT NULL,
+  THROWABLE TEXT
+);
diff --git 
a/core/persistence-jpa-json/src/main/resources/core-majson.properties 
b/core/persistence-jpa-json/src/main/resources/core-majson.properties
new file mode 100644
index 0000000000..e29b704fe7
--- /dev/null
+++ b/core/persistence-jpa-json/src/main/resources/core-majson.properties
@@ -0,0 +1,33 @@
+# 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.
+
+persistence.indexesXML=classpath:majson/indexes.xml
+persistence.viewsXML=classpath:majson/views.xml
+
+persistence.domain[0].key=Master
+persistence.domain[0].jdbcDriver=org.mariadb.jdbc.Driver
+persistence.domain[0].jdbcURL=jdbc:mariadb://localhost:3306/syncope?characterEncoding=UTF-8
+persistence.domain[0].dbUsername=syncope
+persistence.domain[0].dbPassword=syncope
+persistence.domain[0].databasePlatform=org.apache.openjpa.jdbc.sql.MariaDBDictionary(blobTypeName=LONGBLOB,dateFractionDigits=3)
+persistence.domain[0].orm=META-INF/spring-orm-majson.xml
+persistence.domain[0].auditSql=audit_majson.sql
+persistence.domain[0].poolMaxActive=10
+persistence.domain[0].poolMinIdle=2
+
+provisioning.quartz.delegate=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
+provisioning.quartz.sql=tables_mariadb.sql
diff --git a/core/persistence-jpa-json/src/main/resources/majson/indexes.xml 
b/core/persistence-jpa-json/src/main/resources/majson/indexes.xml
new file mode 100644
index 0000000000..8bc611e0ae
--- /dev/null
+++ b/core/persistence-jpa-json/src/main/resources/majson/indexes.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+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.
+-->
+<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd";>
+<properties>
+  <comment>Additional indexes (in respect to JPA's)</comment>
+
+  <entry key="Realm_parent_id">CREATE INDEX Realm_parent_id ON 
Realm(parent_id)</entry>
+
+  <entry key="SyncopeUser_realm_id">CREATE INDEX SyncopeUser_realm_id ON 
SyncopeUser(realm_id)</entry>
+  <entry key="SyncopeUser_username">CREATE UNIQUE INDEX SyncopeUser_username 
ON SyncopeUser(username)</entry>
+
+  <entry key="SyncopeGroup_realm_id">CREATE INDEX SyncopeGroup_realm_id ON 
SyncopeGroup(realm_id)</entry>
+  <entry key="SyncopeGroup_name">CREATE UNIQUE INDEX SyncopeGroup_name ON 
SyncopeGroup(name)</entry>
+
+  <entry key="AnyObject_realm_id">CREATE INDEX AnyObject_realm_id ON 
AnyObject(realm_id)</entry>
+  <entry key="AnyObject_name">CREATE UNIQUE INDEX AnyObject_name ON 
AnyObject(type_id,name)</entry>
+
+  <entry key="UDynGroupMembers_any_id">CREATE INDEX UDynGroupMembers_any_id ON 
UDynGroupMembers(any_id)</entry>
+  <entry key="UDynGroupMembers_group_id">CREATE INDEX 
UDynGroupMembers_group_id ON UDynGroupMembers(group_id)</entry>
+  <entry key="ADynGroupMembers_any_id">CREATE INDEX ADynGroupMembers_any_id ON 
ADynGroupMembers(any_id)</entry>
+  <entry key="ADynGroupMembers_group_id">CREATE INDEX 
ADynGroupMembers_group_id ON ADynGroupMembers(group_id)</entry>
+
+  <entry key="DynRoleMembers_any_id">CREATE INDEX DynRoleMembers_any_id ON 
DynRoleMembers(any_id)</entry>
+  <entry key="DynRoleMembers_role_id">CREATE INDEX DynRoleMembers_role_id ON 
DynRoleMembers(role_id)</entry>
+
+  <entry key="DynRealmMembers_any_id">CREATE INDEX DynRealmMembers_any_id ON 
DynRealmMembers(any_id)</entry>
+  <entry key="DynRealmMembers_realm_id">CREATE INDEX 
DynRealmMembers_dynRealm_id ON DynRealmMembers(dynRealm_id)</entry>
+
+  <entry key="UMembership_GroupIndex">CREATE INDEX UMembership_GroupIndex ON 
UMembership(group_id)</entry>
+  <entry key="UMembership_UserIndex">CREATE INDEX UMembership_UserIndex ON 
UMembership(user_id)</entry>
+  <entry key="AMembership_GroupIndex">CREATE INDEX AMembership_GroupIndex ON 
AMembership(group_id)</entry>
+  <entry key="AMembership_AnyObjectIndex">CREATE INDEX 
AMembership_AnyObjectIndex ON AMembership(anyObject_id)</entry>
+
+  <entry key="URelationship_RightIndex">CREATE INDEX URelationship_RightIndex 
ON URelationship(anyObject_id)</entry>
+  <entry key="URelationship_LeftIndex">CREATE INDEX URelationship_LeftIndex ON 
URelationship(user_id)</entry>
+  <entry key="ARelationship_RightIndex">CREATE INDEX ARelationship_RightIndex 
ON ARelationship(right_anyObject_id)</entry>
+  <entry key="ARelationship_AnyObjectIndex">CREATE INDEX 
ARelationship_AnyObjectIndex ON ARelationship(left_anyObject_id)</entry>
+
+  <entry key="Task_executedIndex">CREATE INDEX Task_executedIndex ON 
NotificationTask(executed)</entry>
+  <entry key="TaskExec1_TaskIdIndex">CREATE INDEX TaskExec1_TaskIdIndex ON 
PropagationTaskExec(task_id)</entry>
+  <entry key="TaskExec2_TaskIdIndex">CREATE INDEX TaskExec2_TaskIdIndex ON 
PullTaskExec(task_id)</entry>
+  <entry key="TaskExec3_TaskIdIndex">CREATE INDEX TaskExec3_TaskIdIndex ON 
PushTaskExec(task_id)</entry>
+  <entry key="TaskExec4_TaskIdIndex">CREATE INDEX TaskExec4_TaskIdIndex ON 
NotificationTaskExec(task_id)</entry>
+  <entry key="TaskExec5_TaskIdIndex">CREATE INDEX TaskExec5_TaskIdIndex ON 
SchedTaskExec(task_id)</entry>
+  <entry key="ATPullTask_PullTaskIndex">CREATE INDEX ATPullTask_PullTaskIndex 
ON AnyTemplatePullTask(pullTask_id)</entry>
+</properties>
diff --git a/core/persistence-jpa-json/src/main/resources/majson/views.xml 
b/core/persistence-jpa-json/src/main/resources/majson/views.xml
new file mode 100644
index 0000000000..ca89fe82ae
--- /dev/null
+++ b/core/persistence-jpa-json/src/main/resources/majson/views.xml
@@ -0,0 +1,262 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+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.
+-->
+<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd";>
+<properties>
+  
+  <entry key="UDynGroupMembers">
+    CREATE TABLE UDynGroupMembers(
+    any_id CHAR(36),
+    group_id CHAR(36),
+    UNIQUE(any_id, group_id))
+  </entry>
+  <entry key="ADynGroupMembers">
+    CREATE TABLE ADynGroupMembers(
+    anyType_id VARCHAR(255),
+    any_id CHAR(36),
+    group_id CHAR(36),
+    UNIQUE(anyType_id, any_id, group_id))
+  </entry>
+  <entry key="DynRoleMembers">
+    CREATE TABLE DynRoleMembers(
+    any_id CHAR(36),
+    role_id VARCHAR(255),
+    UNIQUE(any_id, role_id))
+  </entry>
+  <entry key="DynRealmMembers">
+    CREATE TABLE DynRealmMembers(
+    any_id CHAR(36),
+    dynRealm_id VARCHAR(255),
+    UNIQUE(any_id, dynRealm_id))
+  </entry>
+
+  <!-- user -->
+  <entry key="user_search">
+    CREATE VIEW user_search AS
+ 
+    SELECT u.id as any_id, u.* FROM SyncopeUser u
+  </entry>
+  <entry key="user_search_unique_attr">
+    CREATE VIEW user_search_unique_attr AS
+
+    SELECT u.id as any_id, attrs.*
+    FROM SyncopeUser u LEFT OUTER JOIN JSON_TABLE(COALESCE(plainAttrs, '[]'), 
'$[*]' COLUMNS (
+    schema_id VARCHAR(255) PATH '$.schema',
+    NESTED PATH '$.uniqueValue' COLUMNS (
+    booleanvalue INT PATH '$.booleanValue',
+    datevalue VARCHAR(32) PATH '$.dateValue',
+    doublevalue DOUBLE PATH '$.doubleValue',
+    longvalue BIGINT(20) PATH '$.longValue',
+    stringvalue VARCHAR(255) PATH '$.stringValue'))
+    ) AS attrs ON 1=1
+    WHERE schema_id IS NOT NULL 
+    AND (booleanvalue IS NOT NULL OR datevalue IS NOT NULL OR doublevalue IS 
NOT NULL OR longvalue IS NOT NULL OR stringvalue IS NOT NULL)
+  </entry>
+  <entry key="user_search_attr">
+    CREATE VIEW user_search_attr AS
+    
+    SELECT u.id as any_id, attrs.*
+    FROM SyncopeUser u LEFT OUTER JOIN JSON_TABLE(COALESCE(plainAttrs, '[]'), 
'$[*]' COLUMNS (
+    schema_id VARCHAR(255) PATH '$.schema',
+    NESTED PATH '$.values[*]' COLUMNS (
+    booleanvalue INT PATH '$.booleanValue',
+    datevalue VARCHAR(32) PATH '$.dateValue',
+    doublevalue DOUBLE PATH '$.doubleValue',
+    longvalue BIGINT(20) PATH '$.longValue',
+    stringvalue VARCHAR(255) PATH '$.stringValue'))
+    ) AS attrs ON 1=1
+    WHERE schema_id IS NOT NULL 
+    AND (booleanvalue IS NOT NULL OR datevalue IS NOT NULL OR doublevalue IS 
NOT NULL OR longvalue IS NOT NULL OR stringvalue IS NOT NULL)
+  </entry>
+  <entry key="user_search_urelationship">
+    CREATE VIEW user_search_urelationship AS
+
+    SELECT m.user_id AS any_id, m.anyObject_id AS right_any_id, m.type_id AS 
type
+    FROM URelationship m
+  </entry>
+  <entry key="user_search_umembership">
+    CREATE VIEW user_search_umembership AS
+
+    SELECT m.user_id AS any_id, g.id AS group_id, g.name AS group_name
+    FROM UMembership m, SyncopeGroup g
+    WHERE m.group_id = g.id
+  </entry>
+  <entry key="user_search_role">
+    CREATE VIEW user_search_role AS
+
+    SELECT ss.user_id AS any_id, ss.role_id AS role_id
+    FROM SyncopeUser_SyncopeRole ss
+  </entry>
+  <entry key="user_search_priv">
+    CREATE VIEW user_search_priv AS
+
+    SELECT ss.user_id AS any_id, sp.privilege_id AS privilege_id
+    FROM SyncopeUser_SyncopeRole ss, SyncopeRole_Privilege sp
+    WHERE ss.role_id = sp.role_id
+  </entry>
+  <entry key="user_search_dynpriv">
+    CREATE VIEW user_search_dynpriv AS
+
+    SELECT any_id, privilege_id
+    FROM DynRoleMembers drm, SyncopeRole_Privilege rp
+    WHERE drm.role_id = rp.role_id
+  </entry>
+  <entry key="user_search_auxClass">
+    CREATE VIEW user_search_auxClass AS
+
+    SELECT st.user_id AS any_id, st.anyTypeClass_id AS anyTypeClass_id
+    FROM SyncopeUser_AnyTypeClass st
+  </entry>
+  <entry key="user_search_resource">
+    CREATE VIEW user_search_resource AS
+
+    SELECT st.user_id AS any_id, st.resource_id AS resource_id
+    FROM SyncopeUser_ExternalResource st
+  </entry>
+  <entry key="user_search_group_res">
+    CREATE VIEW user_search_group_res AS
+
+    SELECT m.user_id AS any_id, st.resource_id AS resource_id
+    FROM UMembership m, SyncopeGroup r, SyncopeGroup_ExternalResource st
+    WHERE m.group_id = r.id AND st.group_id = r.id
+  </entry>
+
+  <!-- anyObject -->
+  <entry key="anyObject_search">
+    CREATE VIEW anyObject_search AS
+ 
+    SELECT a.id as any_id, a.* FROM AnyObject a
+  </entry>
+  <entry key="anyObject_search_unique_attr">
+    CREATE VIEW anyObject_search_unique_attr AS
+
+    SELECT u.id as any_id, attrs.*
+    FROM AnyObject u LEFT OUTER JOIN JSON_TABLE(COALESCE(plainAttrs, '[]'), 
'$[*]' COLUMNS (
+    schema_id VARCHAR(255) PATH '$.schema',
+    NESTED PATH '$.uniqueValue' COLUMNS (
+    booleanvalue INT PATH '$.booleanValue',
+    datevalue VARCHAR(32) PATH '$.dateValue',
+    doublevalue DOUBLE PATH '$.doubleValue',
+    longvalue BIGINT(20) PATH '$.longValue',
+    stringvalue VARCHAR(255) PATH '$.stringValue'))
+    ) AS attrs ON 1=1
+    WHERE schema_id IS NOT NULL 
+    AND (booleanvalue IS NOT NULL OR datevalue IS NOT NULL OR doublevalue IS 
NOT NULL OR longvalue IS NOT NULL OR stringvalue IS NOT NULL)
+  </entry>
+  <entry key="anyObject_search_attr">
+    CREATE VIEW anyObject_search_attr AS
+
+    SELECT u.id as any_id, attrs.*
+    FROM AnyObject u LEFT OUTER JOIN JSON_TABLE(COALESCE(plainAttrs, '[]'), 
'$[*]' COLUMNS (
+    schema_id VARCHAR(255) PATH '$.schema',
+    NESTED PATH '$.values[*]' COLUMNS (
+    booleanvalue INT PATH '$.booleanValue',
+    datevalue VARCHAR(32) PATH '$.dateValue',
+    doublevalue DOUBLE PATH '$.doubleValue',
+    longvalue BIGINT(20) PATH '$.longValue',
+    stringvalue VARCHAR(255) PATH '$.stringValue'))
+    ) AS attrs ON 1=1
+    WHERE schema_id IS NOT NULL 
+    AND (booleanvalue IS NOT NULL OR datevalue IS NOT NULL OR doublevalue IS 
NOT NULL OR longvalue IS NOT NULL OR stringvalue IS NOT NULL)
+  </entry>
+  <entry key="anyObject_search_arelationship">
+    CREATE VIEW anyObject_search_arelationship AS
+
+    SELECT m.left_anyObject_id AS any_id, m.right_anyObject_id AS 
right_any_id, m.type_id AS type
+    FROM ARelationship m
+  </entry>
+  <entry key="anyObject_search_amembership">
+    CREATE VIEW anyObject_search_amembership AS
+
+    SELECT m.anyObject_id AS any_id, g.id AS group_id, g.name AS group_name
+    FROM AMembership m, SyncopeGroup g
+    WHERE m.group_id = g.id
+  </entry>
+  <entry key="anyObject_search_auxClass">
+    CREATE VIEW anyObject_search_auxClass AS
+
+    SELECT st.anyObject_id AS any_id, st.anyTypeClass_id AS anyTypeClass_id
+    FROM AnyObject_AnyTypeClass st
+  </entry>
+  <entry key="anyObject_search_resource">
+    CREATE VIEW anyObject_search_resource AS
+
+    SELECT st.anyObject_id AS any_id, st.resource_id AS resource_id
+    FROM AnyObject_ExternalResource st
+  </entry>
+  <entry key="anyObject_search_group_res">
+    CREATE VIEW anyObject_search_group_res AS
+
+    SELECT m.anyObject_id AS any_id, st.resource_id AS resource_id
+    FROM AMembership m, SyncopeGroup r, SyncopeGroup_ExternalResource st
+    WHERE m.group_id = r.id AND st.group_id = r.id
+  </entry>
+
+  <!-- group -->
+  <entry key="group_search">
+    CREATE VIEW group_search AS
+ 
+    SELECT r.id as any_id, r.* FROM SyncopeGroup r
+  </entry>
+  <entry key="group_search_unique_attr">
+    CREATE VIEW group_search_unique_attr AS
+
+    SELECT u.id as any_id, attrs.*
+    FROM SyncopeGroup u LEFT OUTER JOIN JSON_TABLE(COALESCE(plainAttrs, '[]'), 
'$[*]' COLUMNS (
+    schema_id VARCHAR(255) PATH '$.schema',
+    NESTED PATH '$.uniqueValue' COLUMNS (
+    booleanvalue INT PATH '$.booleanValue',
+    datevalue VARCHAR(32) PATH '$.dateValue',
+    doublevalue DOUBLE PATH '$.doubleValue',
+    longvalue BIGINT(20) PATH '$.longValue',
+    stringvalue VARCHAR(255) PATH '$.stringValue'))
+    ) AS attrs ON 1=1
+    WHERE schema_id IS NOT NULL 
+    AND (booleanvalue IS NOT NULL OR datevalue IS NOT NULL OR doublevalue IS 
NOT NULL OR longvalue IS NOT NULL OR stringvalue IS NOT NULL)
+  </entry>
+  <entry key="group_search_attr">
+    CREATE VIEW group_search_attr AS
+
+    SELECT u.id as any_id, attrs.*
+    FROM SyncopeGroup u LEFT OUTER JOIN JSON_TABLE(COALESCE(plainAttrs, '[]'), 
'$[*]' COLUMNS (
+    schema_id VARCHAR(255) PATH '$.schema',
+    NESTED PATH '$.values[*]' COLUMNS (
+    booleanvalue INT PATH '$.booleanValue',
+    datevalue VARCHAR(32) PATH '$.dateValue',
+    doublevalue DOUBLE PATH '$.doubleValue',
+    longvalue BIGINT(20) PATH '$.longValue',
+    stringvalue VARCHAR(255) PATH '$.stringValue'))
+    ) AS attrs ON 1=1
+    WHERE schema_id IS NOT NULL 
+    AND (booleanvalue IS NOT NULL OR datevalue IS NOT NULL OR doublevalue IS 
NOT NULL OR longvalue IS NOT NULL OR stringvalue IS NOT NULL)
+  </entry>
+  <entry key="group_search_auxClass">
+    CREATE VIEW group_search_auxClass AS
+
+    SELECT st.group_id AS any_id, st.anyTypeClass_id AS anyTypeClass_id
+    FROM SyncopeGroup_AnyTypeClass st
+  </entry>
+  <entry key="group_search_resource">
+    CREATE VIEW group_search_resource AS
+
+    SELECT st.group_id AS any_id, st.resource_id AS resource_id
+    FROM SyncopeGroup_ExternalResource st
+  </entry>
+
+</properties>
diff --git 
a/core/persistence-jpa-json/src/test/java/org/apache/syncope/core/persistence/jpa/JPAJSONTestContextCustomizer.java
 
b/core/persistence-jpa-json/src/test/java/org/apache/syncope/core/persistence/jpa/JPAJSONTestContextCustomizer.java
index 8a73334730..798400fcdd 100644
--- 
a/core/persistence-jpa-json/src/test/java/org/apache/syncope/core/persistence/jpa/JPAJSONTestContextCustomizer.java
+++ 
b/core/persistence-jpa-json/src/test/java/org/apache/syncope/core/persistence/jpa/JPAJSONTestContextCustomizer.java
@@ -53,6 +53,12 @@ public class JPAJSONTestContextCustomizer implements 
ContextCustomizer {
                         "provisioning.quartz.sql=tables_mysql_innodb.sql");
                 break;
 
+            case "majson":
+                TestPropertySourceUtils.addInlinedPropertiesToEnvironment(
+                        ctx,
+                        "provisioning.quartz.sql=tables_mariadb.sql");
+                break;
+
             case "ojson":
                 TestPropertySourceUtils.addInlinedPropertiesToEnvironment(
                         ctx,
@@ -65,6 +71,7 @@ public class JPAJSONTestContextCustomizer implements 
ContextCustomizer {
         AnnotatedBeanDefinitionReader reader = new 
AnnotatedBeanDefinitionReader(getBeanDefinitionRegistry(ctx));
         reader.registerBean(PGJPAJSONPersistenceContext.class, 
"PGJPAJSONPersistenceContext");
         reader.registerBean(MyJPAJSONPersistenceContext.class, 
"MyJPAJSONPersistenceContext");
+        reader.registerBean(MaJPAJSONPersistenceContext.class, 
"MaJPAJSONPersistenceContext");
         reader.registerBean(OJPAJSONPersistenceContext.class, 
"OJPAJSONPersistenceContext");
     }
 }
diff --git 
a/core/persistence-jpa-json/src/main/resources/META-INF/spring.factories 
b/core/persistence-jpa-json/src/test/resources/core-majson-test.properties
similarity index 59%
copy from core/persistence-jpa-json/src/main/resources/META-INF/spring.factories
copy to core/persistence-jpa-json/src/test/resources/core-majson-test.properties
index 3eea832444..9f235bece1 100644
--- a/core/persistence-jpa-json/src/main/resources/META-INF/spring.factories
+++ b/core/persistence-jpa-json/src/test/resources/core-majson-test.properties
@@ -15,7 +15,16 @@
 # specific language governing permissions and limitations
 # under the License.
 
-org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
-  org.apache.syncope.core.persistence.jpa.PGJPAJSONPersistenceContext,\
-  org.apache.syncope.core.persistence.jpa.MyJPAJSONPersistenceContext,\
-  org.apache.syncope.core.persistence.jpa.OJPAJSONPersistenceContext
+security.adminUser=${adminUser}
+security.anonymousUser=${anonymousUser}
+security.jwsKey=${jwsKey}
+security.secretKey=${secretKey}
+
+persistence.domain[0].jdbcURL=jdbc:mariadb://${DB_CONTAINER_IP}:3306/syncope?characterEncoding=UTF-8
+persistence.domain[0].poolMaxActive=10
+persistence.domain[0].poolMinIdle=2
+# keep the next two lines until https://jira.mariadb.org/browse/MDEV-27898 is 
fixed
+persistence.domain[0].dbUsername=root
+persistence.domain[0].dbPassword=password
+
+provisioning.connIdLocation=${syncope.connid.location}
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnyDAO.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnyDAO.java
index 2cea7b3837..3362317b5a 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnyDAO.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnyDAO.java
@@ -208,11 +208,8 @@ public abstract class AbstractAnyDAO<A extends Any<?>> 
extends AbstractDAO<A> im
         query.setParameter("schemaKey", schema.getKey());
         query.setParameter("stringValue", attrValue.getStringValue());
         query.setParameter("booleanValue", attrValue.getBooleanValue());
-        if (attrValue.getDateValue() == null) {
-            query.setParameter("dateValue", null);
-        } else {
-            query.setParameter("dateValue", 
attrValue.getDateValue().toInstant());
-        }
+        query.setParameter("dateValue", 
Optional.ofNullable(attrValue.getDateValue()).
+                map(OffsetDateTime::toInstant).orElse(null));
         query.setParameter("longValue", attrValue.getLongValue());
         query.setParameter("doubleValue", attrValue.getDoubleValue());
 
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
index 8fc9582573..55f9c2ef50 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
@@ -948,13 +948,11 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO 
{
                 && !(cond instanceof AnyCond)
                 && cond.getType() != AttrCond.Type.ISNULL && cond.getType() != 
AttrCond.Type.ISNOTNULL) {
 
-            query.append("any_id NOT IN (SELECT DISTINCT any_id FROM ");
-            if (schema.isUniqueConstraint()) {
-                query.append(svs.asSearchViewSupport().uniqueAttr().name);
-            } else {
-                query.append(svs.asSearchViewSupport().attr().name);
-            }
-            query.append(" WHERE schema_id='").append(schema.getKey());
+            query.append("any_id NOT IN (SELECT DISTINCT any_id FROM ").
+                    append((schema.isUniqueConstraint()
+                            ? svs.asSearchViewSupport().uniqueAttr().name
+                            : svs.asSearchViewSupport().attr().name)).
+                    append(" WHERE schema_id='").append(schema.getKey());
             fillAttrQuery(query, attrValue, schema, cond, false, parameters, 
svs);
             query.append(')');
         } else {
@@ -1104,12 +1102,10 @@ public class JPAAnySearchDAO extends 
AbstractAnySearchDAO {
                 if (not && !(cond instanceof AnyCond) && 
checked.getLeft().isMultivalue()) {
                     query.append(svs.field().name).append(" WHERE ");
                 } else {
-                    if (checked.getLeft().isUniqueConstraint()) {
-                        
query.append(svs.asSearchViewSupport().uniqueAttr().name);
-                    } else {
-                        query.append(svs.asSearchViewSupport().attr().name);
-                    }
-                    query.append(" WHERE 
schema_id='").append(checked.getLeft().getKey());
+                    query.append((checked.getLeft().isUniqueConstraint()
+                            ? svs.asSearchViewSupport().uniqueAttr().name
+                            : svs.asSearchViewSupport().attr().name)).
+                            append(" WHERE 
schema_id='").append(checked.getLeft().getKey());
                 }
                 fillAttrQuery(query, checked.getRight(), checked.getLeft(), 
cond, not, parameters, svs);
         }
diff --git 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/UserTest.java
 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/UserTest.java
index 21ca8564b0..9fc2e5e759 100644
--- 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/UserTest.java
+++ 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/UserTest.java
@@ -85,8 +85,8 @@ public class UserTest extends AbstractTest {
         assertNull(user.getSecurityQuestion());
         assertNull(user.getSecurityAnswer());
         assertEquals("admin", user.getCreator());
-        assertEquals("Giacomo", 
user.getPlainAttr("firstname").get().getValuesAsStrings().get(0));
-        assertEquals("Puccini", 
user.getPlainAttr("surname").get().getValuesAsStrings().get(0));
+        assertEquals("Giacomo", 
user.getPlainAttr("firstname").orElseThrow().getValuesAsStrings().get(0));
+        assertEquals("Puccini", 
user.getPlainAttr("surname").orElseThrow().getValuesAsStrings().get(0));
     }
 
     @Test
diff --git 
a/core/persistence-jpa-json/src/main/resources/META-INF/spring.factories 
b/docker/core/src/main/resources/core-majson.properties
similarity index 50%
copy from core/persistence-jpa-json/src/main/resources/META-INF/spring.factories
copy to docker/core/src/main/resources/core-majson.properties
index 3eea832444..dd203b6253 100644
--- a/core/persistence-jpa-json/src/main/resources/META-INF/spring.factories
+++ b/docker/core/src/main/resources/core-majson.properties
@@ -15,7 +15,19 @@
 # specific language governing permissions and limitations
 # under the License.
 
-org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
-  org.apache.syncope.core.persistence.jpa.PGJPAJSONPersistenceContext,\
-  org.apache.syncope.core.persistence.jpa.MyJPAJSONPersistenceContext,\
-  org.apache.syncope.core.persistence.jpa.OJPAJSONPersistenceContext
+persistence.indexesXML=classpath:majson/indexes.xml
+persistence.viewsXML=classpath:majson/views.xml
+
+persistence.domain[0].key=Master
+persistence.domain[0].jdbcDriver=org.mariadb.jdbc.Driver
+persistence.domain[0].jdbcURL=${DB_URL}
+persistence.domain[0].dbUsername=${DB_USER}
+persistence.domain[0].dbPassword=${DB_PASSWORD}
+persistence.domain[0].databasePlatform=org.apache.openjpa.jdbc.sql.MariaDBDictionary(blobTypeName=LONGBLOB,dateFractionDigits=3)
+persistence.domain[0].orm=META-INF/spring-orm-majson.xml
+persistence.domain[0].auditSql=audit_majson.sql
+persistence.domain[0].poolMaxActive=${DB_POOL_MAX}
+persistence.domain[0].poolMinIdle=${DB_POOL_MIN}
+
+provisioning.quartz.delegate=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
+provisioning.quartz.sql=tables_mariadb.sql
diff --git 
a/docker/src/main/resources/docker-compose/docker-compose-mariadb.yml 
b/docker/src/main/resources/docker-compose/docker-compose-majson.yml
similarity index 91%
copy from docker/src/main/resources/docker-compose/docker-compose-mariadb.yml
copy to docker/src/main/resources/docker-compose/docker-compose-majson.yml
index 15cb1c5651..9331524fb0 100644
--- a/docker/src/main/resources/docker-compose/docker-compose-mariadb.yml
+++ b/docker/src/main/resources/docker-compose/docker-compose-majson.yml
@@ -15,7 +15,7 @@
 # specific language governing permissions and limitations
 # under the License.
 
-# Full deployment (Core, Console, Enduser) on MariaDB
+# Full deployment (Core, Console, Enduser) on MariaDB with JSON_TABLE support
 
 services:
    db:
@@ -30,15 +30,16 @@ services:
    syncope:
      depends_on:
        - db
+     command: ["wait-for-it", "db:3306", "-t", "60", "--", 
"/opt/syncope/bin/startup.sh"]
      image: apache/syncope:${SYNCOPE_VERSION}
      ports:
        - "18080:8080"
      restart: always
      environment:
-       SPRING_PROFILES_ACTIVE: docker,mariadb,saml2
+       SPRING_PROFILES_ACTIVE: docker,majson,saml2
        DB_URL: 
jdbc:mariadb://db:3306/syncope?characterEncoding=UTF-8&relaxAutoCommit=true&useSSL=false
-       DB_USER: syncope
-       DB_PASSWORD: syncope
+       DB_USER: root
+       DB_PASSWORD: password
        DB_POOL_MAX: 20
        DB_POOL_MIN: 5
        OPENJPA_REMOTE_COMMIT: sjvm
diff --git 
a/docker/src/main/resources/docker-compose/docker-compose-mariadb.yml 
b/docker/src/main/resources/docker-compose/docker-compose-mariadb.yml
index 15cb1c5651..17447baf60 100644
--- a/docker/src/main/resources/docker-compose/docker-compose-mariadb.yml
+++ b/docker/src/main/resources/docker-compose/docker-compose-mariadb.yml
@@ -30,6 +30,7 @@ services:
    syncope:
      depends_on:
        - db
+     command: ["wait-for-it", "db:3306", "-t", "60", "--", 
"/opt/syncope/bin/startup.sh"]
      image: apache/syncope:${SYNCOPE_VERSION}
      ports:
        - "18080:8080"
diff --git a/fit/core-reference/pom.xml b/fit/core-reference/pom.xml
index f9d70266df..5ca79aae3a 100644
--- a/fit/core-reference/pom.xml
+++ b/fit/core-reference/pom.xml
@@ -1176,7 +1176,124 @@ under the License.
         </plugins>
       </build>
     </profile>
+
+    <profile>
+      <id>majson-it</id>
+
+      <properties>
+        <jdbcdriver.groupId>org.mariadb.jdbc</jdbcdriver.groupId>
+        <jdbcdriver.artifactId>mariadb-java-client</jdbcdriver.artifactId>
+
+        <spring.profiles.active>embedded,majson</spring.profiles.active>
+      </properties>
+
+      <dependencies>
+        <dependency>
+          <groupId>org.apache.syncope.core</groupId>
+          <artifactId>syncope-core-persistence-jpa-json</artifactId>
+          <version>${project.version}</version>
+        </dependency>
     
+        <dependency>
+          <groupId>org.mariadb.jdbc</groupId>
+          <artifactId>mariadb-java-client</artifactId>
+          <version>${jdbc.mariadb.version}</version>
+          <scope>test</scope>
+        </dependency>
+      </dependencies>
+
+      <build>
+        <defaultGoal>clean verify</defaultGoal>
+
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-war-plugin</artifactId>
+            <configuration>
+              
<packagingExcludes>WEB-INF/classes/domains/Two*</packagingExcludes>
+            </configuration>
+          </plugin>
+
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-failsafe-plugin</artifactId>
+            <inherited>true</inherited>
+            <configuration>
+              <includes>
+                <include>**/org/apache/syncope/fit/core/*ITCase.java</include>
+              </includes>
+            </configuration>
+          </plugin>
+
+          <plugin>
+            <groupId>io.fabric8</groupId>
+            <artifactId>docker-maven-plugin</artifactId>
+            <configuration>
+              <images>
+                <image>
+                  <alias>mariadb</alias>
+                  <name>mariadb:${docker.mariadb.version}</name>
+                  <run>
+                    <env>
+                      <MYSQL_ROOT_PASSWORD>password</MYSQL_ROOT_PASSWORD>
+                      <MYSQL_DATABASE>syncope</MYSQL_DATABASE>
+                      <MYSQL_USER>syncope</MYSQL_USER>
+                      <MYSQL_PASSWORD>syncope</MYSQL_PASSWORD>
+                    </env>
+                    <tmpfs>
+                      <mount>/var/lib/mysql:rw</mount>
+                    </tmpfs>
+                    <wait>
+                      <log>MariaDB init process done. Ready for start up.</log>
+                      <time>30000</time>
+                    </wait>
+                  </run>
+                </image>
+              </images>
+            </configuration>
+            <executions>
+              <execution>
+                <id>start-mariadb</id>
+                <phase>pre-integration-test</phase>
+                <goals>
+                  <goal>start</goal>
+                </goals>
+              </execution>
+              <execution>
+                <id>stop-mariadb</id>
+                <phase>post-integration-test</phase>
+                <goals>
+                  <goal>stop</goal>
+                  <goal>remove</goal>
+                </goals>
+              </execution>
+            </executions>
+          </plugin>
+          
+          <plugin>
+            <groupId>org.codehaus.cargo</groupId>
+            <artifactId>cargo-maven3-plugin</artifactId>
+            <inherited>true</inherited>
+            <configuration>
+              <container>
+                <systemProperties>
+                  
<DB_CONTAINER_IP>${docker.container.mariadb.ip}</DB_CONTAINER_IP>
+                </systemProperties>
+              </container>
+            </configuration>
+          </plugin>
+        </plugins>
+
+        <resources>
+          <resource>
+            
<directory>${basedir}/../../core/persistence-jpa-json/src/test/resources/domains</directory>
+            <targetPath>${project.build.outputDirectory}/domains</targetPath>
+            <filtering>true</filtering>
+          </resource>
+        </resources>
+      </build>
+    </profile>
+
     <profile>
       <id>mariadb-it</id>
 
diff --git a/fit/core-reference/src/main/resources/core-majson.properties 
b/fit/core-reference/src/main/resources/core-majson.properties
new file mode 100644
index 0000000000..62efd56e14
--- /dev/null
+++ b/fit/core-reference/src/main/resources/core-majson.properties
@@ -0,0 +1,35 @@
+# 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.
+
+persistence.indexesXML=classpath:majson/indexes.xml
+persistence.viewsXML=classpath:majson/views.xml
+
+persistence.domain[0].key=Master
+persistence.domain[0].jdbcDriver=org.mariadb.jdbc.Driver
+persistence.domain[0].jdbcURL=jdbc:mariadb://${DB_CONTAINER_IP}:3306/syncope?characterEncoding=UTF-8
+# keep the next two lines until https://jira.mariadb.org/browse/MDEV-27898 is 
fixed
+persistence.domain[0].dbUsername=root
+persistence.domain[0].dbPassword=password
+persistence.domain[0].databasePlatform=org.apache.openjpa.jdbc.sql.MariaDBDictionary(blobTypeName=LONGBLOB,dateFractionDigits=3)
+persistence.domain[0].orm=META-INF/spring-orm-majson.xml
+persistence.domain[0].auditSql=audit_majson.sql
+persistence.domain[0].poolMaxActive=10
+persistence.domain[0].poolMinIdle=2
+
+provisioning.quartz.delegate=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
+provisioning.quartz.sql=tables_mariadb.sql
+
diff --git a/src/main/asciidoc/reference-guide/configuration/dbms.adoc 
b/src/main/asciidoc/reference-guide/configuration/dbms.adoc
index f2b803e099..9817fa17e9 100644
--- a/src/main/asciidoc/reference-guide/configuration/dbms.adoc
+++ b/src/main/asciidoc/reference-guide/configuration/dbms.adoc
@@ -248,6 +248,73 @@ 
https://mariadb.com/kb/en/configuring-mariadb-with-option-files/[option file^].
 This assumes that you have a MariaDB instance running on localhost, listening 
on its default port 3306 with a database
 `syncope` fully accessible by user `syncope` with password `syncope`.
 
+==== MariaDB (JSON)
+
+[NOTE]
+With the configurations reported below, Apache Syncope will leverage the
+https://mariadb.com/kb/en/json_table/^] function.
+
+[NOTE]
+Apache Syncope {docVersion} is verified with MariaDB server >= {mariadb} and 
JDBC driver >= {mariadbJDBC}.
+
+Add the following dependency to `core/pom.xml`:
+
+[source,xml,subs="verbatim,attributes"]
+----
+<dependency>
+  <groupId>org.apache.syncope.core</groupId>
+  <artifactId>syncope-core-persistence-jpa-json</artifactId>
+  <version>${syncope.version}</version>
+</dependency>
+----
+
+Create
+
+[source]
+....
+persistence.indexesXML=classpath:majson/indexes.xml
+persistence.viewsXML=classpath:majson/views.xml
+
+persistence.domain[0].key=Master
+persistence.domain[0].jdbcDriver=org.mariadb.jdbc.Driver
+persistence.domain[0].jdbcURL=jdbc:mariadb://localhost:3306/syncope?characterEncoding=UTF-8
+persistence.domain[0].dbUsername=root
+persistence.domain[0].dbPassword=password
+persistence.domain[0].databasePlatform=org.apache.openjpa.jdbc.sql.MariaDBDictionary(blobTypeName=LONGBLOB,dateFractionDigits=3)
+persistence.domain[0].orm=META-INF/spring-orm-majson.xml
+persistence.domain[0].auditSql=audit_majson.sql
+persistence.domain[0].poolMaxActive=10
+persistence.domain[0].poolMinIdle=2
+
+provisioning.quartz.delegate=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
+provisioning.quartz.sql=tables_mariadb.sql
+....
+
+as `core/src/main/resources/core-majson.properties`.
+
+Do not forget to include `majson` as
+https://docs.spring.io/spring-boot/docs/2.7.x/reference/html/features.html#features.profiles.adding-active-profiles[Spring
 Boot profile^]
+for the Core application.
+
+[CAUTION]
+It is important to set the collation to `utf8_general_ci` after creation of 
`syncope` database.
+
+[WARNING]
+====
+It is necessary to use `utf8mb4_unicode_ci` instead of `utf8mb4_general_ci` if 
case-sensitive queries are required.
+In this case, set
+....
+init_connect = "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci"
+....
+under either the `[mysqld]` section or the `[mariadb]` section of your
+https://mariadb.com/kb/en/configuring-mariadb-with-option-files/[option file^].
+====
+
+[WARNING]
+This assumes that you have a MariaDB instance running on localhost, listening 
on its default port 3306 with a database
+`syncope` and super-admin user `root` with password `password`.
+Super-admin user is required until 
https://jira.mariadb.org/browse/MDEV-27898[this bug^] is fixed.
+
 ==== Oracle Database
 
 [NOTE]
diff --git a/src/site/xdoc/building.xml b/src/site/xdoc/building.xml
index b4b8386ca7..a98276d2e4 100644
--- a/src/site/xdoc/building.xml
+++ b/src/site/xdoc/building.xml
@@ -171,7 +171,7 @@ under the License.
           <p>This build profile requires <a 
href="https://www.docker.com/";>Docker</a> to work.</p>
         </div>
         Perform the full test suite against a real <a 
href="https://mariadb.org/";>MariaDB</a> database via
-        <source>$ mvn -Pmariadb-it</source>
+        <source>$ mvn -Pmariadb-it</source> or <source>$ mvn 
-Pmajson-it</source> (for JSON support)
 
         <h5>Oracle database</h5>
         <div class="alert alert-warning">

Reply via email to