I am trying to access Tuple data structure I have stored in Cassandra with Mapper. But, I am unable to. I haven't found any example online.
This is the table and data I have created.
cqlsh:test> CREATE TABLE test.test_nested (id varchar PRIMARY KEY, address_mapping list<frozen<tuple<text,text>>>);
cqlsh:test> INSERT INTO test.test_nested (id, address_mapping) VALUES ('12345', [('Adress 1', 'pin1'), ('Adress 2', 'pin2')]);
cqlsh:test>
cqlsh:test> select * from test.test_nested;
id | address_mapping
-------+----------------------------------------------
12345 | [('Adress 1', 'pin1'), ('Adress 2', 'pin2')]
(1 rows)
My mapped class(using lombok for builder, getter, setter):
#Builder
#Table(keyspace = "test", name = "test_nested")
public class TestNested {
#PartitionKey
#Column(name = "id")
#Getter
#Setter
private String id;
#Column(name = "address_mapping")
#Frozen
#Getter
#Setter
private List<Object> address_mapping;
}
My Mapper class:
public class TestNestedStore {
private final Mapper<TestNested> mapper;
public TestNestedStore(Mapper<TestNested> mapper) {
this.mapper = mapper;
}
public void insert(TestNested userDropData) {
mapper.save(userDropData);
}
public void remove(String id) {
mapper.delete(id);
}
public TestNested findByUserId(String id) {
return mapper.get(id);
}
public ListenableFuture<TestNested> findByUserIdAsync(String id) {
return mapper.getAsync(id);
}
}
I am trying to access data in a test method as follows:
#Test
public void testConnection2(){
MappingManager manager = new MappingManager(scyllaDBConnector.getSession());
Mapper<TestNested> mapper = manager.mapper(TestNested.class);
TestNestedStore testNestedStore = new TestNestedStore(mapper);
ListenableFuture<TestNested> fut = testNestedStore.findByUserIdAsync("12345");
Futures.addCallback(fut, new FutureCallback<TestNested>() {
#Override
public void onSuccess(TestNested testNested) {
}
#Override
public void onFailure(Throwable throwable) {
System.out.println("Call failed");
}
});
}
Bit, I am unable to access the tuple. I get this error:
java.lang.IllegalArgumentException: Error while checking frozen types on field address_mapping of entity com.example.model.TestNested: expected List to be not frozen but was frozen
at com.datastax.driver.mapping.AnnotationChecks.validateAnnotations(AnnotationChecks.java:73)
at com.datastax.driver.mapping.AnnotationParser.parseEntity(AnnotationParser.java:81)
at com.datastax.driver.mapping.MappingManager.getMapper(MappingManager.java:148)
at com.datastax.driver.mapping.MappingManager.mapper(MappingManager.java:105)
I have also tried with private List<TupleValue> address_mapping;. But of no use!
How do I access Tuple values through object mapper of cassandra?
You define address_mapping as list<frozen<tuple<text,text>>>, that is, a list of frozen tuple values. To communicate this to the MappingManager, you can use the #FrozenValue attribute.
TestNested should look like:
#Builder
#Table(keyspace = "test", name = "test_nested")
public class TestNested {
...
#Column(name = "address_mapping")
#Frozen
#Getter
#Setter
#FrozenValue
private List<Object> address_mapping;
}
For defining the cassandra datatype of
map<text,frozen<tuple<text,text,int,text>>>
in java entity class mention the datatype as,
import com.datastax.driver.core.TupleValue;
#FrozenValue
private Map<String,TupleValue> event_driven;
I have a customer's database that has a collection, in which the document fields can vary between each other. There are some constant fields I can rely on, but as for the rest - I have no way of narrowing the field list as the customer wants the solution to be dynamic.
My question is - can I somehow implement a generic mapping that would return, let's say, a map of document's fields using Spring Data?
edit:
Thanks for the tips. I've tried getting the generic Object (hoping I'd be able to convert it into a map) using the entity:
#Document(collection = "Data")
public class DataEntity {
#Id
private String id;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
but fetching the object by the ID using MongoRepository produces an object with data field set to null.
I'm using SpringBoot 1.3.1.RELEASE with spring-boot-starter-data-mongodb 1.3.1.RELEASE.
You can use a Map for dynamic properties like below. Is this what you are looking for?
#Document(collection = "computers")
public class Computer {
#Id
private String id;
#Field("name")
private String name;
//Other constant fields
#Field("properties")
private Map<String, Object> properties;
}
I already have such an error, when you try to perform unapropriate matching (date to boolean, and so) which I had been able to fix quite easilly.
But this time, I am quite confused, because hibernate refuses to match a "numeric" Id to a Java "Long" (and it also failed when setter is made for Double, Integer, Float, String, int, long, etc.)
The sql-server field "id" is a NUMERIC(19,0)
My DTO is :
#XmlRootElement
#XmlAccessorType(XmlAccessType.PROPERTY)
public class DtoResult {
private Long id;
private String name;
// ...
public Long getId() {
return id;
}
public void setId(final Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
}
My hibernate query :
final SQLQuery query= getCurrentSession().createSQLQuery(select + from + where);
query.setParameter("manyFields", manyFields);
query
.addScalar("id")
.addScalar("name")
.setResultTransformer(Transformers.aliasToBean(DtoResult.class));
return query.list(); // List<DtoResult>
Error:
IllegalArgumentException occurred while calling setter for property [com.some.thing.DtoResult.id (expected type = java.lang.Long)]; target = [com.some.thing.DtoResult#77a70b79], property value = [269895]
I am really puzzled about this, thus any help is welcome.
Thanks for reading untill there.
Just add the expected type, like:
.addScalar("id", new LongType())
.addScalar("name", new StringType())
The number from database query is not Long but BigInteger.
Change setter to:
public void setId(final Number id) {
this.id = id != null ? id.longValue() : null;
}
I need to make an hibernate SQLQuery with db2 and this query is returning me some fields which are calculated and have no relation with any columns in database.
The goal is setting the values of these sum() calculations from SQLQuery on three new transient fields in a Java Object which already existed.
The SQLQuery uses the syntax:
SELECT id as {entityObject.id},
name as {entityObject.name},
order as {entityObject.order},
SUM(CASE
WHEN pv.value_id = 1
AND pv.value=1 THEN 1 ELSE 0 END) AS {entityObject.someCount}
The problem is that Hibernate complains and says to need a column for someCount. It seems not to help declaring the java field as transient or even using the #Transient annotation from javax.persistence at the same time.
If I only declare in the hbm.xml mapping file:
<property name="id" type="java.lang.Integer" column="someColumn" />
<!-- Some more fields here -->
<!-- THE IMPORTANT ONE -->
<property name="someCount" type="java.lang.Integer"/>
Java Object:
public class EntityObject implements Serializable {
private static final long serialVersionUID = 1479579608940145961L;
private Integer id;
private String name;
private Integer order;
// This is the one giving me hell. I've tried with #Transient also
private transient Integer someCount;
public Category() {
}
public Category(final String name, final Integer order) {
this.name = name;
this.order = order;
}
public Integer getOrder() {
return this.order;
}
public void setOrder(final Integer order) {
this.order = order;
}
public Integer getId() {
return this.id;
}
public String getName() {
return this.name;
}
public void setName(final String name) {
this.name = name;
}
public Integer getSomeCount() {
return someCount;
}
public void setSomeCount(final Integer count) {
this.someCount = count;
}
}
It asks me for a column, and I have tried inserting a fake column and it does not work. The thing is that I want these 'count' fields only to be set from the SQLQuery and to be empty and null when coming from a regular Hibernate Query.
I have looked at the docs and googled, and it seems that you can declare a field transient by only not declaring it at the hibernate mapping file, but then it does not set it on the object with the "as {entityObject.someCount}" even when I have getters/setters declared.
Help please.
Thanks very much in advance.
The only option available that might do all this directly from the Database without having to issue additional queries is a Hibernate Formula property:
http://wiki.jrapid.com/w/Formula_(attribute_of_property)
<property name="someCount" formula="select count(*) from some_table where table_key = ?"/>
The ? placeholder will be populated automatically with the ID of the current instance.
1 Create a POJO:
public class SumValue{
private BigInteger myId;
private String myName;
private BigInteger myOrder;
private BigInteger mySum;
....
getters and setters here
....
}
2 Minor changes in your query
SELECT id as "myId",
name as "myName",
order as "myOrder",
SUM(CASE
WHEN pv.value_id = 1
AND pv.value=1 THEN 1 ELSE 0 END) AS "mySum"
3 Execute native sql
List<SumValue> jobStateViewList = (List<SumValue>)getSessionFactory().getCurrentSession()
.createSQLQuery(yourQuery)
.setResultTransformer(
new AliasToBeanResultTransformer(SumValue.class)
).list();
Given the following example POJO's: (Assume Getters and Setters for all properties)
class User {
String user_name;
String display_name;
}
class Message {
String title;
String question;
User user;
}
One can easily query a database (postgres in my case) and populate a list of Message classes using a BeanPropertyRowMapper where the db field matched the property in the POJO: (Assume the DB tables have corresponding fields to the POJO properties).
NamedParameterDatbase.query("SELECT * FROM message", new BeanPropertyRowMapper(Message.class));
I'm wondering - is there a convenient way to construct a single query and / or create a row mapper in such a way to also populate the properties of the inner 'user' POJO within the message.
That is, Some syntatical magic where each result row in the query:
SELECT * FROM message, user WHERE user_id = message_id
Produce a list of Message with the associated User populated
Use Case:
Ultimately, the classes are passed back as a serialised object from a Spring Controller, the classes are nested so that the resulting JSON / XML has a decent structure.
At the moment, this situation is resolved by executing two queries and manually setting the user property of each message in a loop. Useable, but I imagine a more elegant way should be possible.
Update : Solution Used -
Kudos to #Will Keeling for inspiration for the answer with use of the custom row mapper - My solution adds the addition of bean property maps in order to automate the field assignments.
The caveat is structuring the query so that the relevant table names are prefixed (however there is no standard convention to do this so the query is built programatically):
SELECT title AS "message.title", question AS "message.question", user_name AS "user.user_name", display_name AS "user.display_name" FROM message, user WHERE user_id = message_id
The custom row mapper then creates several bean maps and sets their properties based on the prefix of the column: (using meta data to get the column name).
public Object mapRow(ResultSet rs, int i) throws SQLException {
HashMap<String, BeanMap> beans_by_name = new HashMap();
beans_by_name.put("message", BeanMap.create(new Message()));
beans_by_name.put("user", BeanMap.create(new User()));
ResultSetMetaData resultSetMetaData = rs.getMetaData();
for (int colnum = 1; colnum <= resultSetMetaData.getColumnCount(); colnum++) {
String table = resultSetMetaData.getColumnName(colnum).split("\\.")[0];
String field = resultSetMetaData.getColumnName(colnum).split("\\.")[1];
BeanMap beanMap = beans_by_name.get(table);
if (rs.getObject(colnum) != null) {
beanMap.put(field, rs.getObject(colnum));
}
}
Message m = (Task)beans_by_name.get("message").getBean();
m.setUser((User)beans_by_name.get("user").getBean());
return m;
}
Again, this might seem like overkill for a two class join but the IRL use case involves multiple tables with tens of fields.
Perhaps you could pass in a custom RowMapper that could map each row of an aggregate join query (between message and user) to a Message and nested User. Something like this:
List<Message> messages = jdbcTemplate.query("SELECT * FROM message m, user u WHERE u.message_id = m.message_id", new RowMapper<Message>() {
#Override
public Message mapRow(ResultSet rs, int rowNum) throws SQLException {
Message message = new Message();
message.setTitle(rs.getString(1));
message.setQuestion(rs.getString(2));
User user = new User();
user.setUserName(rs.getString(3));
user.setDisplayName(rs.getString(4));
message.setUser(user);
return message;
}
});
A bit late to the party however I found this when I was googling the same question and I found a different solution that may be favorable for others in the future.
Unfortunately there is not a native way to achieve the nested scenario without making a customer RowMapper. However I will share an easier way to make said custom RowMapper than some of the other solutions here.
Given your scenario you can do the following:
class User {
String user_name;
String display_name;
}
class Message {
String title;
String question;
User user;
}
public class MessageRowMapper implements RowMapper<Message> {
#Override
public Message mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = (new BeanPropertyRowMapper<>(User.class)).mapRow(rs,rowNum);
Message message = (new BeanPropertyRowMapper<>(Message.class)).mapRow(rs,rowNum);
message.setUser(user);
return message;
}
}
The key thing to remember with BeanPropertyRowMapper is that you have to follow the naming of your columns and the properties of your class members to the letter with the following exceptions (see Spring Documentation):
column names are aliased exactly
column names with underscores will be converted into "camel" case (ie. MY_COLUMN_WITH_UNDERSCORES == myColumnWithUnderscores)
Spring introduced a new AutoGrowNestedPaths property into the BeanMapper interface.
As long as the SQL query formats the column names with a . separator (as before) then the Row mapper will automatically target inner objects.
With this, I created a new generic row mapper as follows:
QUERY:
SELECT title AS "message.title", question AS "message.question", user_name AS "user.user_name", display_name AS "user.display_name" FROM message, user WHERE user_id = message_id
ROW MAPPER:
package nested_row_mapper;
import org.springframework.beans.*;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.JdbcUtils;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
public class NestedRowMapper<T> implements RowMapper<T> {
private Class<T> mappedClass;
public NestedRowMapper(Class<T> mappedClass) {
this.mappedClass = mappedClass;
}
#Override
public T mapRow(ResultSet rs, int rowNum) throws SQLException {
T mappedObject = BeanUtils.instantiate(this.mappedClass);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(mappedObject);
bw.setAutoGrowNestedPaths(true);
ResultSetMetaData meta_data = rs.getMetaData();
int columnCount = meta_data.getColumnCount();
for (int index = 1; index <= columnCount; index++) {
try {
String column = JdbcUtils.lookupColumnName(meta_data, index);
Object value = JdbcUtils.getResultSetValue(rs, index, Class.forName(meta_data.getColumnClassName(index)));
bw.setPropertyValue(column, value);
} catch (TypeMismatchException | NotWritablePropertyException | ClassNotFoundException e) {
// Ignore
}
}
return mappedObject;
}
}
Update: 10/4/2015. I typically don't do any of this rowmapping anymore. You can accomplish selective JSON representation much more elegantly via annotations. See this gist.
I spent the better part of a full day trying to figure this out for my case of 3-layer nested objects and just finally nailed it. Here's my situation:
Accounts (i.e. users) --1tomany--> Roles --1tomany--> views (user is allowed to see)
(These POJO classes are pasted at the very bottom.)
And I wanted the controller to return an object like this:
[ {
"id" : 3,
"email" : "catchall#sdcl.org",
"password" : "sdclpass",
"org" : "Super-duper Candy Lab",
"role" : {
"id" : 2,
"name" : "ADMIN",
"views" : [ "viewPublicReports", "viewAllOrders", "viewProducts", "orderProducts", "viewOfferings", "viewMyData", "viewAllData", "home", "viewMyOrders", "manageUsers" ]
}
}, {
"id" : 5,
"email" : "catchall#stereolab.com",
"password" : "stereopass",
"org" : "Stereolab",
"role" : {
"id" : 1,
"name" : "USER",
"views" : [ "viewPublicReports", "viewProducts", "orderProducts", "viewOfferings", "viewMyData", "home", "viewMyOrders" ]
}
}, {
"id" : 6,
"email" : "catchall#ukmedschool.com",
"password" : "ukmedpass",
"org" : "University of Kentucky College of Medicine",
"role" : {
"id" : 2,
"name" : "ADMIN",
"views" : [ "viewPublicReports", "viewAllOrders", "viewProducts", "orderProducts", "viewOfferings", "viewMyData", "viewAllData", "home", "viewMyOrders", "manageUsers" ]
}
} ]
A key point is to realize that Spring doesn't just do all this automatically for you. If you just ask it to return an Account item without doing the work of nested objects, you'll merely get:
{
"id" : 6,
"email" : "catchall#ukmedschool.com",
"password" : "ukmedpass",
"org" : "University of Kentucky College of Medicine",
"role" : null
}
So, first, create your 3-table SQL JOIN query and make sure you're getting all the data you need. Here's mine, as it appears in my Controller:
#PreAuthorize("hasAuthority('ROLE_ADMIN')")
#RequestMapping("/accounts")
public List<Account> getAllAccounts3()
{
List<Account> accounts = jdbcTemplate.query("SELECT Account.id, Account.password, Account.org, Account.email, Account.role_for_this_account, Role.id AS roleid, Role.name AS rolename, role_views.role_id, role_views.views FROM Account JOIN Role on Account.role_for_this_account=Role.id JOIN role_views on Role.id=role_views.role_id", new AccountExtractor() {});
return accounts;
}
Note that I'm JOINing 3 tables. Now create a RowSetExtractor class to put the nested objects together. The above examples show 2-layer nesting... this one goes a step further and does 3 levels. Note that I'm having to maintain the second-layer object in a map as well.
public class AccountExtractor implements ResultSetExtractor<List<Account>>{
#Override
public List<Account> extractData(ResultSet rs) throws SQLException, DataAccessException {
Map<Long, Account> accountmap = new HashMap<Long, Account>();
Map<Long, Role> rolemap = new HashMap<Long, Role>();
// loop through the JOINed resultset. If the account ID hasn't been seen before, create a new Account object.
// In either case, add the role to the account. Also maintain a map of Roles and add view (strings) to them when encountered.
Set<String> views = null;
while (rs.next())
{
Long id = rs.getLong("id");
Account account = accountmap.get(id);
if(account == null)
{
account = new Account();
account.setId(id);
account.setPassword(rs.getString("password"));
account.setEmail(rs.getString("email"));
account.setOrg(rs.getString("org"));
accountmap.put(id, account);
}
Long roleid = rs.getLong("roleid");
Role role = rolemap.get(roleid);
if(role == null)
{
role = new Role();
role.setId(rs.getLong("roleid"));
role.setName(rs.getString("rolename"));
views = new HashSet<String>();
rolemap.put(roleid, role);
}
else
{
views = role.getViews();
views.add(rs.getString("views"));
}
views.add(rs.getString("views"));
role.setViews(views);
account.setRole(role);
}
return new ArrayList<Account>(accountmap.values());
}
}
And this gives the desired output. POJOs below for reference. Note the #ElementCollection Set views in the Role class. This is what automatically generates the role_views table as referenced in the SQL query. Knowing that table exists, its name and its field names is crucial to getting the SQL query right. It feels wrong to have to know that... it seems like this should be more automagic -- isn't that what Spring is for?... but I couldn't figure out a better way. You've got to do the work manually in this case, as far as I can tell.
#Entity
public class Account implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
#Column(unique=true, nullable=false)
private String email;
#Column(nullable = false)
private String password;
#Column(nullable = false)
private String org;
private String phone;
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "roleForThisAccount") // #JoinColumn means this side is the *owner* of the relationship. In general, the "many" side should be the owner, or so I read.
private Role role;
public Account() {}
public Account(String email, String password, Role role, String org)
{
this.email = email;
this.password = password;
this.org = org;
this.role = role;
}
// getters and setters omitted
}
#Entity
public class Role implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id; // required
#Column(nullable = false)
#Pattern(regexp="(ADMIN|USER)")
private String name; // required
#Column
#ElementCollection(targetClass=String.class)
private Set<String> views;
#OneToMany(mappedBy="role")
private List<Account> accountsWithThisRole;
public Role() {}
// constructor with required fields
public Role(String name)
{
this.name = name;
views = new HashSet<String>();
// both USER and ADMIN
views.add("home");
views.add("viewOfferings");
views.add("viewPublicReports");
views.add("viewProducts");
views.add("orderProducts");
views.add("viewMyOrders");
views.add("viewMyData");
// ADMIN ONLY
if(name.equals("ADMIN"))
{
views.add("viewAllOrders");
views.add("viewAllData");
views.add("manageUsers");
}
}
public long getId() { return this.id;}
public void setId(long id) { this.id = id; };
public String getName() { return this.name; }
public void setName(String name) { this.name = name; }
public Set<String> getViews() { return this.views; }
public void setViews(Set<String> views) { this.views = views; };
}
I worked a lot on stuff like this and do not see an elegant way to achieve this without an OR mapper.
Any simple solution based on reflection would heavily rely on the 1:1 (or maybe N:1) relation. Further your columns returned are not qualified by their type, so you cannot say which columns matches which class.
You may get away with spring-data and QueryDSL. I did not dig into them, but I think you need some meta-data for the query that is later used to map back the columns from your database into a proper data structure.
You may also try the new PostgreSQL json support that looks promising.
NestedRowMapper worked for me, the important part is getting the SQL correct. The Message properties shouldn't have the class name in them so the query should look like this:
QUERY:
SELECT title AS "title", question AS "question", user_name AS "user.user_name", display_name AS "user.display_name" FROM message, user WHERE user_id = message_id