JPA: Saving List of values as comma separated values - java

I am receiving a simple list of values part of JSON request which I want to save as comma separated values. Tried using following but it did not work.
#Column(nullable = true)
#GeneratedValue(strategy = GenerationType.AUTO)
private ArrayList<String> services = new ArrayList<String>() ;
and
#Column(nullable = true)
#ElementCollection(targetClass = String.class)
private List<String> services = new ArrayList<String>() ;
#ElementCollection threw an exception saying table services does not exist.

The #ElementCollection requires a table to store multiples rows of values,
So you could define as a String column and join/explode in getters and setters, like this
private String services;
public setServices(String services[]) //Can be Array or List
{
// this.services = Iterate services[] and create a comma separated string or Use ArrayUtils
}
public String[] getServices() //Can be Array or List
{
// services.split(",") to get a list of Strings, then typecast/parse them to Strings before returning or use Arrays.asList(arguments.split(","));
}

As mentioned by others in the comments an AttributeConverter works pretty well. This one uses Jackson to serialize as a JSON array. I recommend JSON since it cleanly handles delimiter escaping, nulls, quotes, etc.:
#Converter
public class StringListAttributeConverter implements AttributeConverter<List<String>, String> {
private static final TypeReference<List<String>> TypeRef = new TypeReference<List<String>>(){};
#Override
public String convertToDatabaseColumn (List<String> attribute) {
if (attribute == null) {
return null;
}
try {
return ObjectMapperFactory.getInstance().writeValueAsString(attribute);
}
catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
#Override
public List<String> convertToEntityAttribute (String dbData) {
if (dbData == null) {
return null;
}
try {
return ObjectMapperFactory.getInstance().readValue(dbData, TypeRef);
}
catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
}
I've used this class and it works well in most cases. One caveat I've found is that using this converter can confuse some JPA criteria queries, because it expects a type List on the entity, but finds a String in the db.

A more simple variant did the trick for me, no Jackson, but trimming strings:
public class CsvTrimmedStringsConverter implements AttributeConverter<List<String>, String> {
#Override
public String convertToDatabaseColumn(List<String> attribute) {
return attribute == null
? null
: attribute.stream().map(String::trim).collect(Collectors.joining(","));
}
#Override
public List<String> convertToEntityAttribute(String dbData) {
return dbData == null
? null
: Arrays.stream(dbData.split(",")).map(String::trim).collect(Collectors.toList());
}
}

Related

How do I have two identical json property names assigned to two separated fields

I have the following Java class
I need to serialize it to json in the following way:
if the list(paymentTransactionReport field) is not null display it values -
{
"paymentTransactionResponse" : [
{},
{}
]
}
if the list is null I need to display the paymentTransactionReportError in json field with name 'paymentTransactionResponse', as in previous case. Example -
{
"paymentTransactionResponse" : {
----
//fields from PaymentTransactionReportError class
----
}
}
How can I do this?preferably without custom serializers.
If use just two annotations #JsonProperty with the same name and JsonInclude.NON_NULL as I did, I have this error: No converter found for return value of type:... Seems to be that is a error happened during serialization because of fields with the same name
One way you can achieve this is, using #JsonAnyGetter, Try this
public class TestDTO {
#JsonIgnore
List<String> paymentTransactionResponse;
#JsonIgnore
String paymentTransactionResponseError;
public List<String> getPaymentTransactionResponse() {
return paymentTransactionResponse;
}
public void setPaymentTransactionResponse(List<String> paymentTransactionResponse) {
this.paymentTransactionResponse = paymentTransactionResponse;
}
public String getPaymentTransactionResponseError() {
return paymentTransactionResponseError;
}
public void setPaymentTransactionResponseError(String paymentTransactionResponseError) {
this.paymentTransactionResponseError = paymentTransactionResponseError;
}
#JsonAnyGetter
public Map<String, Object> getData(){
Map<String, Object> map = new HashMap<String, Object>();
if(paymentTransactionResponse != null) {
map.put("paymentTransactionResponse", paymentTransactionResponse);
}else {
map.put("paymentTransactionResponse", paymentTransactionResponseError);
}
return map;
}}

How to store a List<String> from spring JPA to text[] array in database

I am trying to write an Java- Array List into postgres database. But i am not able to DO, i don't get error as well. I am trying to post data
"skill":["java,dotnet"]
using postman tool but on Post nothing happens and i don't see any data in db.I also don't get any error in my console.please help me out on this i m wondering how to do this
I am able to read Array data from database but cannot write
/*
* Spring Entity:
*/
#Column(name="skill" , columnDefinition = "text[]")
#Convert(converter =ListToStringConverter.class)
private List<String> skill=new ArrayList<>();
ublic List<String> getSkill() {
return skill;
}
public void setSkill(List<String> skill) {
this.skill= skill;
}
#Converter
public class ListToStringConverter implements AttributeConverter<List<String>, String> {
#Override
public String convertToDatabaseColumn(List<String> attribute) {
if (attribute == null || attribute.isEmpty()) {
return "";
}
return StringUtils.join(attribute, ",");
}
#Override
public List<String> convertToEntityAttribute(String dbData) {
if (dbData == null || dbData.trim().length() == 0) {
return new ArrayList<String>();
}
String[] data = dbData.split(",");
return Arrays.asList(data);
}
}
Controller Code:
This is my Controller code to create a object in database and i use a interface service that is call from controller to Save to db-Postgres which is a JPA repo
#RequestMapping(value = "/reqCreate", method = RequestMethod.POST)
public ResponseEntity<Requirement> addRequirement(#RequestBody Requirement requirement) {
reqService.save(requirement);
return new ResponseEntity<Requirement>(requirement, HttpStatus.CREATED);
}
Service:
public void save(Requirement r) {
reqRepo.save(r);
}
The columnDefinition seems to be wrong.
You want to convert Collection into String, but you still declare column of array type.
Replace "text[]" with "text" and check if it works?

Android Room Database: How to handle Arraylist in an Entity?

I just implemented Room for offline data saving. But in an Entity class, I am getting the following error:
Error:(27, 30) error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
And the class is as following:
#Entity(tableName = "firstPageData")
public class MainActivityData {
#PrimaryKey
private String userId;
#ColumnInfo(name = "item1_id")
private String itemOneId;
#ColumnInfo(name = "item2_id")
private String itemTwoId;
// THIS IS CAUSING THE ERROR... BASICALLY IT ISN'T READING ARRAYS
#ColumnInfo(name = "mylist_array")
private ArrayList<MyListItems> myListItems;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public ArrayList<MyListItems> getMyListItems() {
return myListItems;
}
public void setCheckListItems(ArrayList<MyListItems> myListItems) {
this.myListItems = myListItems;
}
}
So basically I want to save the ArrayList in the database but I was not able to find anything relevant to it. Can you guide me regarding how to save an Array using Room?
NOTE: MyListItems Pojo class contains 2 Strings (as of now)
Type Converter are made specifically for that. In your case, you can use code snippet given below to store data in DB.
public class Converters {
#TypeConverter
public static ArrayList<String> fromString(String value) {
Type listType = new TypeToken<ArrayList<String>>() {}.getType();
return new Gson().fromJson(value, listType);
}
#TypeConverter
public static String fromArrayList(ArrayList<String> list) {
Gson gson = new Gson();
String json = gson.toJson(list);
return json;
}
}
And mention this class in your Room DB like this
#Database (entities = {MainActivityData.class},version = 1)
#TypeConverters({Converters.class})
More info here
Option #1: Have MyListItems be an #Entity, as MainActivityData is. MyListItems would set up a #ForeignKey back to MainActivityData. In this case, though, MainActivityData cannot have private ArrayList<MyListItems> myListItems, as in Room, entities do not refer to other entities. A view model or similar POJO construct could have a MainActivityData and its associated ArrayList<MyListItems>, though.
Option #2: Set up a pair of #TypeConverter methods to convert ArrayList<MyListItems> to and from some basic type (e.g., a String, such as by using JSON as a storage format). Now, MainActivityData can have its ArrayList<MyListItems> directly. However, there will be no separate table for MyListItems, and so you cannot query on MyListItems very well.
Kotlin version for type converter:
class Converters {
#TypeConverter
fun listToJson(value: List<JobWorkHistory>?) = Gson().toJson(value)
#TypeConverter
fun jsonToList(value: String) = Gson().fromJson(value, Array<JobWorkHistory>::class.java).toList()
}
I Used JobWorkHistory object for my purpose, use the object of your own
#Database(entities = arrayOf(JobDetailFile::class, JobResponse::class), version = 1)
#TypeConverters(Converters::class)
abstract class MyRoomDataBase : RoomDatabase() {
abstract fun attachmentsDao(): AttachmentsDao
}
Native Kotlin version using Kotlin's serialization component – kotlinx.serialization.
Add the Kotlin serialization Gradle plugin and dependency to your build.gradle:
apply plugin: 'kotlinx-serialization'
dependencies {
...
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1"
}
Add the Type converters to your Converter class;
class Converters {
#TypeConverter
fun fromList(value : List<String>) = Json.encodeToString(value)
#TypeConverter
fun toList(value: String) = Json.decodeFromString<List<String>>(value)
}
Add your Converter class to your database class:
#TypeConverters(Converters::class)
abstract class YourDatabase: RoomDatabase() {...}
And you're done!
Extra resources:
Type Converters
Kotlin Serialization
Better version of List<String> converter
class StringListConverter {
#TypeConverter
fun fromString(stringListString: String): List<String> {
return stringListString.split(",").map { it }
}
#TypeConverter
fun toString(stringList: List<String>): String {
return stringList.joinToString(separator = ",")
}
}
Kotlin Answer
You need to do 3 things:
Create Converters class.
Add Converters class on Database.
Just define what you want to use in Entity class.
Usage example step by step:
Step 1 :
class Converters {
#TypeConverter
fun listToJsonString(value: List<YourModel>?): String = Gson().toJson(value)
#TypeConverter
fun jsonStringToList(value: String) = Gson().fromJson(value, Array<YourModel>::class.java).toList()
}
Step 2 :
#Database(entities = [YourEntity::class], version = 1)
#TypeConverters(Converters::class)
abstract class YourDatabase : RoomDatabase() {
abstract fun yourDao(): YourDao
}
Step 3 :
Note: You do not need to call functions of Converter which are listToJsonString() and jsonStringToList(). They are using in background by Room.
#Entity(tableName = "example_database_table")
data class YourEntity(
#PrimaryKey(autoGenerate = true) val id: Long = 0,
#ColumnInfo(name = "your_model_list") var yourModelList: List<YourModel>,
)
I would personally advise against #TypeConverters/serializations, since they break the database's normal forms compliance.
For this particular case it might be worth defining a relationship using the #Relation annotation, which allows to query nested entities into a single object without the added complexity of declaring a #ForeignKey and writing all the SQL queries manually:
#Entity
public class MainActivityData {
#PrimaryKey
private String userId;
private String itemOneId;
private String itemTwoId;
}
#Entity
public class MyListItem {
#PrimaryKey
public int id;
public String ownerUserId;
public String text;
}
/* This is the class we use to define our relationship,
which will also be used to return our query results.
Note that it is not defined as an #Entity */
public class DataWithItems {
#Embedded public MainActivityData data;
#Relation(
parentColumn = "userId"
entityColumn = "ownerUserId"
)
public List<MyListItem> myListItems;
}
/* This is the DAO interface where we define the queries.
Even though it looks like a single SELECT, Room performs
two, therefore the #Transaction annotation is required */
#Dao
public interface ListItemsDao {
#Transaction
#Query("SELECT * FROM MainActivityData")
public List<DataWithItems> getAllData();
}
Aside from this 1-N example, it is possible to define 1-1 and N-M relationships as well.
This is how i handle List conversion
public class GenreConverter {
#TypeConverter
public List<Integer> gettingListFromString(String genreIds) {
List<Integer> list = new ArrayList<>();
String[] array = genreIds.split(",");
for (String s : array) {
if (!s.isEmpty()) {
list.add(Integer.parseInt(s));
}
}
return list;
}
#TypeConverter
public String writingStringFromList(List<Integer> list) {
String genreIds = "";
for (int i : list) {
genreIds += "," + i;
}
return genreIds;
}}
And then on the database i do as shown below
#Database(entities = {MovieEntry.class}, version = 1)
#TypeConverters(GenreConverter.class)
And below is a kotlin implementation of the same;
class GenreConverter {
#TypeConverter
fun gettingListFromString(genreIds: String): List<Int> {
val list = mutableListOf<Int>()
val array = genreIds.split(",".toRegex()).dropLastWhile {
it.isEmpty()
}.toTypedArray()
for (s in array) {
if (s.isNotEmpty()) {
list.add(s.toInt())
}
}
return list
}
#TypeConverter
fun writingStringFromList(list: List<Int>): String {
var genreIds=""
for (i in list) genreIds += ",$i"
return genreIds
}}
Had the same error message as described above.
I would like to add: if you get this error message in a #Query, you should add #TypeConverters above the #Query annotation.
Example:
#TypeConverters(DateConverter.class)
#Query("update myTable set myDate=:myDate where id = :myId")
void updateStats(int myId, Date myDate);
....
public class DateConverter {
#TypeConverter
public static Date toDate(Long timestamp) {
return timestamp == null ? null : new Date(timestamp);
}
#TypeConverter
public static Long toTimestamp(Date date) {
return date == null ? null : date.getTime();
}
}
This answer uses Kotin to split by comma and construct the comma delineated string. The comma needs to go at the end of all but the last element, so this will handle single element lists as well.
object StringListConverter {
#TypeConverter
#JvmStatic
fun toList(strings: String): List<String> {
val list = mutableListOf<String>()
val array = strings.split(",")
for (s in array) {
list.add(s)
}
return list
}
#TypeConverter
#JvmStatic
fun toString(strings: List<String>): String {
var result = ""
strings.forEachIndexed { index, element ->
result += element
if(index != (strings.size-1)){
result += ","
}
}
return result
}
}
in my case problem was generic type
base on this answer
https://stackoverflow.com/a/48480257/3675925
use List instead of ArrayList
import androidx.room.TypeConverter
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
class IntArrayListConverter {
#TypeConverter
fun fromString(value: String): List<Int> {
val type = object: TypeToken<List<Int>>() {}.type
return Gson().fromJson(value, type)
}
#TypeConverter
fun fromArrayList(list: List<Int>): String {
val type = object: TypeToken<List<Int>>() {}.type
return Gson().toJson(list, type)
}
}
it doesn't need add #TypeConverters(IntArrayListConverter::class) to query in dao class nor fields in Entity class
and just add #TypeConverters(IntArrayListConverter::class) to database class
#Database(entities = [MyEntity::class], version = 1, exportSchema = false)
#TypeConverters(IntArrayListConverter::class)
abstract class MyDatabase : RoomDatabase() {
All above answers are for list of strings. But below helps you to write converter for list of objects.
Just in place of "YourClassName", add your Object class.
#TypeConverter
public String fromValuesToList(ArrayList<**YourClassName**> value) {
if (value== null) {
return (null);
}
Gson gson = new Gson();
Type type = new TypeToken<ArrayList<**YourClassName**>>() {}.getType();
return gson.toJson(value, type);
}
#TypeConverter
public ArrayList<**YourClassName**> toOptionValuesList(String value) {
if (value== null) {
return (null);
}
Gson gson = new Gson();
Type type = new TypeToken<List<**YourClassName**>>() {
}.getType();
return gson.fromJson(value, type);
}
When we are using TypeConverters then datatype should be return type of TypeConverter method.
For example: TypeConverter method returns a string, then adding table column should be of type string.
private static final Migration MIGRATION_1_2 = new Migration(1, 2) {
#Override
public void migrate(#NonNull SupportSQLiteDatabase database) {
// Since we didn't alter the table, there's nothing else to do here.
database.execSQL("ALTER TABLE "+ Collection.TABLE_STATUS + " ADD COLUMN deviceType TEXT;");
database.execSQL("ALTER TABLE "+ Collection.TABLE_STATUS + " ADD COLUMN inboxType TEXT;");
}
};
Adding #TypeConverters with the converter class as params
to Database & to the Dao class, made my queries work
Json conversions don't scale well in terms of memory allocation.I'd rather go for something similar to responses above with some nullability.
class Converters {
#TypeConverter
fun stringAsStringList(strings: String?): List<String> {
val list = mutableListOf<String>()
strings
?.split(",")
?.forEach {
list.add(it)
}
return list
}
#TypeConverter
fun stringListAsString(strings: List<String>?): String {
var result = ""
strings?.forEach { element ->
result += "$element,"
}
return result.removeSuffix(",")
}
}
For simple data types the above can be used, otherwise for complex datatypes Room provides Embedded
Here is the example for adding the customObject types to Room DB table.
https://mobikul.com/insert-custom-list-and-get-that-list-in-room-database-using-typeconverter/
Adding a type converter was easy, I just needed a method that could turn the list of objects into a string, and a method that could do the reverse. I used gson for this.
public class Converters {
#TypeConverter
public static String MyListItemListToString(List<MyListitem> list) {
Gson gson = new Gson();
return gson.toJson(list);
}
#TypeConverter
public static List<Integer> stringToMyListItemList(#Nullable String data) {
if (data == null) {
return Collections.emptyList();
}
Type listType = new TypeToken<List<MyListItem>>() {}.getType();
Gson gson = new Gson();
return gson.fromJson(data, listType);
}
}
I then added an annotation to the field in the Entity:
#TypeConverters(Converters.class)
public final ArrayList<MyListItem> myListItems;
#Query("SELECT * FROM business_table")
abstract List<DatabaseModels.Business> getBusinessInternal();
#Transaction #Query("SELECT * FROM business_table")
public ArrayList<DatabaseModels.Business> getBusiness(){
return new ArrayList<>(getBusinessInternal());
}
All answers above correct. Yes, if you REALLY need store array of something into one SQLite field TypeConverter is a solution.
And I used the accepted answer in my projects.
But don't do it!!!
If you need store array in Entity in 90% cases you need to create one-to-many or many-to-many relationships.
Otherwise, your next SQL query for select something with key inside this array will be absolutely hell...
Example:
Object foo comes as json: [{id: 1, name: "abs"}, {id:2, name: "cde"}
Object bar: [{id, 1, foos: [1, 2], {...}]
So don't make entity like:
#Entity....
data class bar(
...
val foos: ArrayList<Int>)
Make like next:
#Entity(tablename="bar_foo", primaryKeys=["fooId", "barId"])
data class barFoo(val barId: Int, val fooId: Int)
And sore your foos:[] as records in this table.
I wanted to store a List containing photos URI in ROOM database . Because of special characters I was getting this error localized in my converter class, TypeConverters.class:
com.google.gson.stream.MalformedJsonException: Unterminated array at line 1 column 8 path $[1]
There was a problem in my simple converter for arrayList of String, in fact it was :
public static List<String> fromString(String value) {
Type listType = new TypeToken<ArrayList<String>>() {
}.getType();
return new Gson().fromJson(value, listType);
}
#TypeConverter
public static String fromArrayList(List<String> list) {
Gson gson = new Gson();
return gson.toJson(list);
}
By taking #Derrick Njeru comment back, I changed it for that it could take in consideration a String as "https://images.app.goo.gl/jwhkhzhZVWrceQV67" like this :
#TypeConverter
public List<String> gettingListFromString(String genreIds) {
List<String> list = new ArrayList<>();
String[] array = genreIds.split(",");
for (String s : array) {
if (!s.isEmpty()) {
list.add(s);
}
}
return list;
}
#TypeConverter
public String writingStringFromList(List<String> list) {
String genreIds = "";
for (String i : list) {
genreIds += "," + i;
}
return genreIds;
}
Use official solution from room, #Embedded annotation :
#Embedded(prefix = "mylist_array") private ArrayList<MyListItems> myListItems

Concatenate object values regarding name of fields in alphabetical order

I'm looking for a method to concatenate object values regarding name of fields in alphabetical order.
Example:
public Class Request {
private String number;
private String amount;
private String currency;
}
Request request = new Request();
request.setNumber("tata");
request.setCurrency("toto");
With this, my method should return tototata.
Method must be generic:
public static String concatenate(Object object) { ...}
null values must not be concatenated.
I already checked out Apache Commons BeanUtils and Java 8 streams, but found nothing nice.
Thanks you Andrew Tobilko, i was doing this (working)
public static String concatenateAlphabetically(Object object) {
Map<String, String> map = null;
try {
map = BeanUtils.describe(object);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
e.printStackTrace();
}
// remove class attribute generating from BeanUtils.describe
map.remove("class");
map.values().removeAll(Collections.singleton(null));
Map<String, String> treeMap = new TreeMap<String, String>(map);
return treeMap.values().stream().collect(Collectors.joining());
}
But i don't like to use BeanUtils like this, i prefer your method.
I just add f.setAccessible(true); to access to private fields
You may write this by iterating over all declared fields in the given object.
public static String concatenate(Object object) {
return Arrays.stream(object.getClass().getDeclaredFields())
.filter(f -> f.getType() == String.class)
.sorted(Comparator.comparing(Field::getName))
.map(f -> {
try { return (String)f.get(object); }
catch (IllegalAccessException e) { return null; }
})
.filter(Objects::nonNull)
.collect(Collectors.joining());
}

Jackson: Serialize comma separated string to json array

Currently I have form like below:
public class Form {
private String listOfItems;
public String getListOfItems() {
return listOfItems;
}
public void setListOfItems(String listOfItems) {
this.listOfItems= listOfItems;
}
}
For instanse listOfItems equals to the following string "1,2,3".
The goal is to serialize this form to following format:
{
"listOfItems": [1, 2, 3]
}
It would be good to know how to correctly do such thing? As I know it is possible to create some custom serializer then mark appropriate getter method with it, like this #JsonSerialize(using = SomeCustomSerializer).
But not sure whether it is correct approach, probably any default implementations already exist.
If you can edit your Form class:
public class Form {
private String listOfItems;
public String getListOfItems() {
return listOfItems;
}
public void setListOfItems(String listOfItems) {
this.listOfItems = listOfItems;
}
#JsonProperty("listOfItems")
public List<Integer> getArrayListOfItems() {
if (listOfItems != null) {
List<Integer> items = new ArrayList();
for (String s : listOfItems.split(",")) {
items.add(Integer.parseInt(s)); // May throw NumberFormatException
}
return items;
}
return null;
}
}
By default Jackson looks for getters for serializing. You can override this by using #JsonProperty annotation.
ObjectMapper mapper = new ObjectMapper();
Form form = new Form();
form.setListOfItems("1,2,3");
System.out.print(mapper.writeValueAsString(form));
Outputs:
{"listOfItems":[1,2,3]}

Categories

Resources