You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openjpa.apache.org by so...@apache.org on 2026/10/25 01:23:03 UTC
(openjpa) branch master updated: [OPENJPA-3020] Choose the LOB type of a converted attribute from the converter (#222)
This is an automated email from the ASF dual-hosted git repository.
solomax pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/openjpa.git
The following commit(s) were added to refs/heads/master by this push:
new a6faa27ff [OPENJPA-3020] Choose the LOB type of a converted attribute from the converter (#222)
a6faa27ff is described below
commit a6faa27ff99c779ace3067a03f9c7deb60c55b47
Author: Richard Zowalla <13...@users.noreply.github.com>
AuthorDate: Sun Oct 25 02:22:57 2026 +0100
[OPENJPA-3020] Choose the LOB type of a converted attribute from the converter (#222)
* [OPENJPA-3020] Choose the LOB type of a converted attribute from the converter
@Lob and <lob/> made a CLOB for a String, char[] or Character[] attribute
and a BLOB otherwise, before the converter was known, and the converter's
template column did not override it. A converter to String on any other
attribute type therefore got a BLOB, and a converter to byte[] on a String
a CLOB. The converter handlers now set the LOB type from the converter's
database type. A byte[] database type is also read as bytes, so the
converter no longer gets a java.sql.Blob.
* [OPENJPA-3020] Note the LOB type change of converted attributes in the migration considerations
* [OPENJPA-3020] Add braces to single statement blocks
---
.../jdbc/meta/strats/ConverterElementHandler.java | 4 +-
.../jdbc/meta/strats/ConverterValueHandler.java | 42 ++++++-
.../persistence/conversion/ItemConverter.java | 39 ++++++
.../persistence/conversion/LobConvertedEntity.java | 133 +++++++++++++++++++++
.../conversion/StringBytesConverter.java | 39 ++++++
.../persistence/conversion/TestLobConverted.java | 121 +++++++++++++++++++
.../conversion/TestLobConvertedXml.java | 89 ++++++++++++++
.../conversion/XmlLobConvertedEntity.java | 74 ++++++++++++
.../src/test/resources/META-INF/persistence.xml | 8 ++
.../persistence/conversion/lob-converted-orm.xml | 37 ++++++
.../src/doc/manual/migration_considerations.xml | 12 ++
11 files changed, 595 insertions(+), 3 deletions(-)
diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/ConverterElementHandler.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/ConverterElementHandler.java
index e8aad8614..d019b549d 100644
--- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/ConverterElementHandler.java
+++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/ConverterElementHandler.java
@@ -28,7 +28,6 @@ import org.apache.openjpa.jdbc.meta.ValueMapping;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.ColumnIO;
import org.apache.openjpa.jdbc.sql.DBDictionary;
-import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.util.MetaDataException;
/**
@@ -66,9 +65,10 @@ public class ConverterElementHandler extends AbstractValueHandler {
DBIdentifier colName = DBIdentifier.newColumn(name,
dict != null ? dict.delimitAll() : false);
+ ConverterValueHandler.adjustLobColumns(vm, _dbType);
Column col = new Column();
col.setIdentifier(colName);
- col.setJavaType(JavaTypes.getTypeCode(_dbType));
+ col.setJavaType(ConverterValueHandler.getColumnJavaType(_dbType));
return new Column[]{ col };
}
diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/ConverterValueHandler.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/ConverterValueHandler.java
index 57b65a9c5..649f3cc56 100644
--- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/ConverterValueHandler.java
+++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/ConverterValueHandler.java
@@ -18,7 +18,10 @@
*/
package org.apache.openjpa.jdbc.meta.strats;
+import java.sql.Types;
+
import org.apache.openjpa.jdbc.identifier.DBIdentifier;
+import org.apache.openjpa.jdbc.meta.JavaSQLTypes;
import org.apache.openjpa.jdbc.meta.ValueMapping;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.ColumnIO;
@@ -54,12 +57,49 @@ public class ConverterValueHandler extends AbstractValueHandler {
DBIdentifier colName = DBIdentifier.newColumn(name,
dict != null ? dict.delimitAll() : false);
+ adjustLobColumns(vm, _dbType);
Column col = new Column();
col.setIdentifier(colName);
- col.setJavaType(JavaTypes.getTypeCode(_dbType));
+ col.setJavaType(getColumnJavaType(_dbType));
return new Column[]{ col };
}
+ /**
+ * The column java type for the converter's database type. A byte array
+ * is read and written as bytes, not as an object.
+ */
+ static int getColumnJavaType(Class<?> dbType) {
+ if (dbType == byte[].class) {
+ return JavaSQLTypes.BYTES;
+ }
+ return JavaTypes.getTypeCode(dbType);
+ }
+
+ /**
+ * A {@code @Lob} or {@code <lob/>} column gets its CLOB or BLOB type from
+ * the declared type of the attribute, because the mapping parsers run
+ * before the converter is known. With a converter the stored value has
+ * the converter's database type, so choose the kind of LOB from that.
+ * Columns that are not LOBs, and LOBs whose database type is neither
+ * String nor byte[], are left alone.
+ */
+ static void adjustLobColumns(ValueMapping vm, Class<?> dbType) {
+ int lobType;
+ if (dbType == String.class) {
+ lobType = Types.CLOB;
+ } else if (dbType == byte[].class) {
+ lobType = Types.BLOB;
+ } else {
+ return;
+ }
+
+ for (Column col : vm.getValueInfo().getColumns()) {
+ if (col.getType() == Types.BLOB || col.getType() == Types.CLOB) {
+ col.setType(lobType);
+ }
+ }
+ }
+
@Override
public boolean isVersionable(ValueMapping vm) {
return true;
diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/conversion/ItemConverter.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/conversion/ItemConverter.java
new file mode 100644
index 000000000..3c848f97f
--- /dev/null
+++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/conversion/ItemConverter.java
@@ -0,0 +1,39 @@
+/*
+ * 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.openjpa.persistence.conversion;
+
+import jakarta.persistence.AttributeConverter;
+import jakarta.persistence.Converter;
+
+/**
+ * Converts a single, mutable {@link Item} to its name.
+ */
+@Converter
+public class ItemConverter implements AttributeConverter<Item, String> {
+
+ @Override
+ public String convertToDatabaseColumn(Item attribute) {
+ return attribute == null ? null : attribute.getName();
+ }
+
+ @Override
+ public Item convertToEntityAttribute(String dbData) {
+ return dbData == null ? null : new Item(dbData);
+ }
+}
diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/conversion/LobConvertedEntity.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/conversion/LobConvertedEntity.java
new file mode 100644
index 000000000..f1e281856
--- /dev/null
+++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/conversion/LobConvertedEntity.java
@@ -0,0 +1,133 @@
+/*
+ * 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.openjpa.persistence.conversion;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import jakarta.persistence.Convert;
+import jakarta.persistence.ElementCollection;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.Lob;
+
+/**
+ * Converted LOB attributes. The kind of LOB follows the converter's database
+ * type, not the attribute type. {@code plainBytes} is converted to bytes
+ * without being a LOB; the last fields have no converter.
+ */
+@Entity
+public class LobConvertedEntity {
+
+ @Id
+ private long id;
+
+ @Lob
+ @Convert(converter = ItemListConverter.class)
+ private List<Item> items = new ArrayList<>();
+
+ @Lob
+ @Convert(converter = ItemConverter.class)
+ private Item item;
+
+ @ElementCollection
+ @Lob
+ @Convert(converter = StringBytesConverter.class)
+ private List<String> elements = new ArrayList<>();
+
+ @Lob
+ @Convert(converter = StringBytesConverter.class)
+ private String bytes;
+
+ @Convert(converter = StringBytesConverter.class)
+ private String plainBytes;
+
+ @Lob
+ private String text;
+
+ @Lob
+ private byte[] data;
+
+ @Lob
+ private Item serial;
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public List<Item> getItems() {
+ return items;
+ }
+
+ public Item getItem() {
+ return item;
+ }
+
+ public void setItem(Item item) {
+ this.item = item;
+ }
+
+ public List<String> getElements() {
+ return elements;
+ }
+
+ public String getBytes() {
+ return bytes;
+ }
+
+ public void setBytes(String bytes) {
+ this.bytes = bytes;
+ }
+
+ public String getPlainBytes() {
+ return plainBytes;
+ }
+
+ public void setPlainBytes(String plainBytes) {
+ this.plainBytes = plainBytes;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ public byte[] getData() {
+ return data;
+ }
+
+ public void setData(byte[] data) {
+ this.data = data;
+ }
+
+ public Item getSerial() {
+ return serial;
+ }
+
+ public void setSerial(Item serial) {
+ this.serial = serial;
+ }
+}
diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/conversion/StringBytesConverter.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/conversion/StringBytesConverter.java
new file mode 100644
index 000000000..0894f0dd2
--- /dev/null
+++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/conversion/StringBytesConverter.java
@@ -0,0 +1,39 @@
+/*
+ * 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.openjpa.persistence.conversion;
+
+import java.nio.charset.StandardCharsets;
+
+import jakarta.persistence.AttributeConverter;
+import jakarta.persistence.Converter;
+
+/** Stores a string as its UTF-8 bytes. */
+@Converter
+public class StringBytesConverter implements AttributeConverter<String, byte[]> {
+
+ @Override
+ public byte[] convertToDatabaseColumn(String attribute) {
+ return attribute == null ? null : attribute.getBytes(StandardCharsets.UTF_8);
+ }
+
+ @Override
+ public String convertToEntityAttribute(byte[] dbData) {
+ return dbData == null ? null : new String(dbData, StandardCharsets.UTF_8);
+ }
+}
diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/conversion/TestLobConverted.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/conversion/TestLobConverted.java
new file mode 100644
index 000000000..c4ef5ffee
--- /dev/null
+++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/conversion/TestLobConverted.java
@@ -0,0 +1,121 @@
+/*
+ * 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.openjpa.persistence.conversion;
+
+import java.sql.Types;
+import java.util.Arrays;
+
+import jakarta.persistence.EntityManager;
+
+import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
+import org.apache.openjpa.jdbc.meta.ClassMapping;
+import org.apache.openjpa.jdbc.meta.FieldMapping;
+import org.apache.openjpa.jdbc.meta.JavaSQLTypes;
+import org.apache.openjpa.jdbc.schema.Column;
+import org.apache.openjpa.persistence.test.SingleEMFTestCase;
+
+/**
+ * A {@code @Lob} attribute with a converter is a CLOB or a BLOB depending on
+ * the converter's database type. The kind of LOB used to follow the attribute
+ * type, so a list or a bean converted to a string got a BLOB (BYTEA on
+ * PostgreSQL) and a string converted to bytes got a CLOB.
+ */
+public class TestLobConverted extends SingleEMFTestCase {
+
+ @Override
+ public void setUp() {
+ setUp(LobConvertedEntity.class, DROP_TABLES);
+ }
+
+ private Column column(String field, boolean element) {
+ ClassMapping cm = ((JDBCConfiguration) emf.getConfiguration())
+ .getMappingRepositoryInstance()
+ .getMapping(LobConvertedEntity.class, null, true);
+ FieldMapping fm = (FieldMapping) cm.getField(field);
+ Column[] cols = element ? fm.getElementMapping().getColumns() : fm.getColumns();
+ assertEquals(1, cols.length);
+ return cols[0];
+ }
+
+ public void testListConvertedToStringIsClob() {
+ assertEquals(Types.CLOB, column("items", false).getType());
+ }
+
+ public void testItemConvertedToStringIsClob() {
+ assertEquals(Types.CLOB, column("item", false).getType());
+ }
+
+ public void testElementConvertedToBytesIsBlob() {
+ assertEquals(Types.BLOB, column("elements", true).getType());
+ }
+
+ public void testStringConvertedToBytesIsBlob() {
+ assertEquals(Types.BLOB, column("bytes", false).getType());
+ }
+
+ /**
+ * A converter to a byte array is read as bytes, with or without
+ * {@code @Lob}. The column used to be typed as an object, so reading
+ * the value back passed the driver's Blob to the converter.
+ */
+ public void testStringConvertedToBytesWithoutLobIsReadAsBytes() {
+ assertEquals(JavaSQLTypes.BYTES, column("plainBytes", false).getJavaType());
+ }
+
+ public void testLobsWithoutConverterAreUnchanged() {
+ assertEquals(Types.CLOB, column("text", false).getType());
+ assertEquals(Types.BLOB, column("data", false).getType());
+ assertEquals(Types.BLOB, column("serial", false).getType());
+ }
+
+ public void testRoundTrip() {
+ try (EntityManager em = emf.createEntityManager()) {
+ em.getTransaction().begin();
+ LobConvertedEntity e = new LobConvertedEntity();
+ e.setId(1L);
+ e.getItems().add(new Item("one"));
+ e.getItems().add(new Item("two"));
+ e.setItem(new Item("single"));
+ e.getElements().add("first");
+ e.getElements().add("second");
+ e.setBytes("binary");
+ e.setPlainBytes("plain");
+ e.setText("text");
+ e.setData(new byte[] { 1, 2, 3 });
+ e.setSerial(new Item("serialized"));
+ em.persist(e);
+ em.getTransaction().commit();
+ }
+ try (EntityManager em = emf.createEntityManager()) {
+ LobConvertedEntity found = em.find(LobConvertedEntity.class, 1L);
+ assertEquals(2, found.getItems().size());
+ assertEquals("one", found.getItems().get(0).getName());
+ assertEquals("two", found.getItems().get(1).getName());
+ assertEquals("single", found.getItem().getName());
+ assertEquals(2, found.getElements().size());
+ assertTrue(found.getElements().contains("first"));
+ assertTrue(found.getElements().contains("second"));
+ assertEquals("binary", found.getBytes());
+ assertEquals("plain", found.getPlainBytes());
+ assertEquals("text", found.getText());
+ assertTrue(Arrays.equals(new byte[] { 1, 2, 3 }, found.getData()));
+ assertEquals("serialized", found.getSerial().getName());
+ }
+ }
+}
diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/conversion/TestLobConvertedXml.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/conversion/TestLobConvertedXml.java
new file mode 100644
index 000000000..ee7b9728b
--- /dev/null
+++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/conversion/TestLobConvertedXml.java
@@ -0,0 +1,89 @@
+/*
+ * 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.openjpa.persistence.conversion;
+
+import java.sql.Types;
+
+import jakarta.persistence.EntityManager;
+
+import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
+import org.apache.openjpa.jdbc.meta.ClassMapping;
+import org.apache.openjpa.jdbc.meta.FieldMapping;
+import org.apache.openjpa.persistence.test.SingleEMFTestCase;
+
+/**
+ * Converted attributes declared as LOBs in a mapping file, with the
+ * converters on the fields. The mapping file is read before the annotations,
+ * so the kind of LOB can only follow the converter once the field is mapped.
+ */
+public class TestLobConvertedXml extends SingleEMFTestCase {
+
+ @Override
+ public void setUp() {
+ setUp(XmlLobConvertedEntity.class, DROP_TABLES);
+ }
+
+ @Override
+ protected String getPersistenceUnitName() {
+ return "lob-converted-xml";
+ }
+
+ private int columnType(String field) {
+ ClassMapping cm = ((JDBCConfiguration) emf.getConfiguration())
+ .getMappingRepositoryInstance()
+ .getMapping(XmlLobConvertedEntity.class, null, true);
+ FieldMapping fm = (FieldMapping) cm.getField(field);
+ assertEquals(1, fm.getColumns().length);
+ return fm.getColumns()[0].getType();
+ }
+
+ public void testListConvertedToStringIsClob() {
+ assertEquals(Types.CLOB, columnType("items"));
+ }
+
+ public void testItemConvertedToStringIsClob() {
+ assertEquals(Types.CLOB, columnType("item"));
+ }
+
+ public void testStringConvertedToBytesIsBlob() {
+ assertEquals(Types.BLOB, columnType("bytes"));
+ }
+
+ public void testRoundTrip() {
+ try (EntityManager em = emf.createEntityManager()) {
+ em.getTransaction().begin();
+ XmlLobConvertedEntity e = new XmlLobConvertedEntity();
+ e.setId(1L);
+ e.getItems().add(new Item("one"));
+ e.getItems().add(new Item("two"));
+ e.setItem(new Item("single"));
+ e.setBytes("binary");
+ em.persist(e);
+ em.getTransaction().commit();
+ }
+ try (EntityManager em = emf.createEntityManager()) {
+ XmlLobConvertedEntity found = em.find(XmlLobConvertedEntity.class, 1L);
+ assertEquals(2, found.getItems().size());
+ assertEquals("one", found.getItems().get(0).getName());
+ assertEquals("two", found.getItems().get(1).getName());
+ assertEquals("single", found.getItem().getName());
+ assertEquals("binary", found.getBytes());
+ }
+ }
+}
diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/conversion/XmlLobConvertedEntity.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/conversion/XmlLobConvertedEntity.java
new file mode 100644
index 000000000..7c8052376
--- /dev/null
+++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/conversion/XmlLobConvertedEntity.java
@@ -0,0 +1,74 @@
+/*
+ * 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.openjpa.persistence.conversion;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import jakarta.persistence.Convert;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+
+/**
+ * Converted attributes that a mapping file declares as LOBs, while the
+ * converters stay on the fields.
+ */
+@Entity
+public class XmlLobConvertedEntity {
+
+ @Id
+ private long id;
+
+ @Convert(converter = ItemListConverter.class)
+ private List<Item> items = new ArrayList<>();
+
+ @Convert(converter = ItemConverter.class)
+ private Item item;
+
+ @Convert(converter = StringBytesConverter.class)
+ private String bytes;
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public List<Item> getItems() {
+ return items;
+ }
+
+ public Item getItem() {
+ return item;
+ }
+
+ public void setItem(Item item) {
+ this.item = item;
+ }
+
+ public String getBytes() {
+ return bytes;
+ }
+
+ public void setBytes(String bytes) {
+ this.bytes = bytes;
+ }
+}
diff --git a/openjpa-persistence-jdbc/src/test/resources/META-INF/persistence.xml b/openjpa-persistence-jdbc/src/test/resources/META-INF/persistence.xml
index 7514d1fe6..b89699669 100644
--- a/openjpa-persistence-jdbc/src/test/resources/META-INF/persistence.xml
+++ b/openjpa-persistence-jdbc/src/test/resources/META-INF/persistence.xml
@@ -432,6 +432,14 @@
</properties>
</persistence-unit>
+ <persistence-unit name="lob-converted-xml">
+ <mapping-file>org/apache/openjpa/persistence/conversion/lob-converted-orm.xml</mapping-file>
+ <class>org.apache.openjpa.persistence.conversion.XmlLobConvertedEntity</class>
+ <properties>
+ <property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema"/>
+ </properties>
+ </persistence-unit>
+
<persistence-unit name="nonstandard-mapping-xml">
<mapping-file>META-INF/nonstandard-orm.xml</mapping-file>
diff --git a/openjpa-persistence-jdbc/src/test/resources/org/apache/openjpa/persistence/conversion/lob-converted-orm.xml b/openjpa-persistence-jdbc/src/test/resources/org/apache/openjpa/persistence/conversion/lob-converted-orm.xml
new file mode 100644
index 000000000..96f50659e
--- /dev/null
+++ b/openjpa-persistence-jdbc/src/test/resources/org/apache/openjpa/persistence/conversion/lob-converted-orm.xml
@@ -0,0 +1,37 @@
+<?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="https://jakarta.ee/xml/ns/persistence/orm"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence/orm https://jakarta.ee/xml/ns/persistence/orm/orm_3_2.xsd"
+ version="3.2">
+ <entity class="org.apache.openjpa.persistence.conversion.XmlLobConvertedEntity">
+ <attributes>
+ <basic name="items">
+ <lob/>
+ </basic>
+ <basic name="item">
+ <lob/>
+ </basic>
+ <basic name="bytes">
+ <lob/>
+ </basic>
+ </attributes>
+ </entity>
+</entity-mappings>
diff --git a/openjpa-project/src/doc/manual/migration_considerations.xml b/openjpa-project/src/doc/manual/migration_considerations.xml
index 0cee386f0..2609406d3 100644
--- a/openjpa-project/src/doc/manual/migration_considerations.xml
+++ b/openjpa-project/src/doc/manual/migration_considerations.xml
@@ -913,6 +913,18 @@
to a column default can therefore not be queried for its null attributes by
<literal>IS NULL</literal>; compare against the substitute value instead.
</para>
+ <para>
+ For a <literal>@Lob</literal> (or <literal><lob/></literal>) attribute with a
+ converter, the kind of LOB now follows the converter's database type: a converter
+ to <classname>String</classname> yields a CLOB (<literal>TEXT</literal> on
+ PostgreSQL and MySQL) and a converter to <literal>byte[]</literal> a BLOB
+ (<literal>BYTEA</literal> on PostgreSQL). In 4.1.x the attribute type decided,
+ so a converter to <classname>String</classname> on any attribute type other than
+ <classname>String</classname> produced a BLOB, and a converter to
+ <literal>byte[]</literal> on a <classname>String</classname> attribute a CLOB.
+ Tables created by 4.1.x for such attributes no longer match the mapping; alter
+ the column type, or pin it with <literal>@Column(columnDefinition=...)</literal>.
+ </para>
<para>
Audit existing <literal>@Converter(autoApply=true)</literal> classes: attributes of
the matching type that were stored unconverted by 4.1.x are now written and read