I'm new to Cassandra and I'm trying to evaluate it for my model. I'm testing with CQL3 and Cassandra 2.0
Let's assume I have the following domain model
public class CompositeSample1 {
private String id;
private List<ChildT1> childrenT1;
private List<ChildT2> childrenT2;
private String rootAttr1;
//getters setters omitted for brevity
}
public class ChildT1 {
private String key;
private String attr1;
private String attr2;
//getters setters omitted for brevity
}
public class ChildT2 {
private String key;
private String attrA;
private String attrB;
//getters setters omitted for brevity
}
So I want to store the above model in Cassandra in a single row with partition key being CompositeSample1.id. The children from the 2 relationships I want to store them as composite columns
id, rootAttr1, childT1|1 {attr1, attr2}, childT1|2 {attr1, attr2} .. childT2|1 {attrA, attrB}, childT2|2 {attrA, attrB}..
I've tried the following:
Created the schema with the following code:
session.execute(String.format("CREATE KEYSPACE IF NOT EXISTS %s WITH replication " +
"= {'class':'SimpleStrategy', 'replication_factor':3};", DbContext.DATABASE_NAME));
session.execute("USE "+DbContext.DATABASE_NAME);
session.execute("CREATE TABLE IF NOT EXISTS compositeSample1 (id uuid, rootAttr1 text, childT1Key text, childT1Attr1 text, , childT1Attr2 text "+
", childT2Key text, childT2AttrA text, childT2AttrB text, PRIMARY KEY ( (id), childT1Key, childT2Key))");
Tried to insert the following model instance
CompositeSample1 compositeSample1 = new CompositeSample1();
compositeSample1.setId(UUID.randomUUID().toString());
compositeSample1.setRootAttr1("A380");
compositeSample1.setChildrenT1(new ArrayList<ChildT1>());
compositeSample1.setChildrenT2(new ArrayList<ChildT2>());
for (int i=1;i<10; i++)
{
ChildT1 child1 = new ChildT1();
child1.setKey("childT1|"+i);
child1.setAttr1("T1_1_"+i);
child1.setAttr2("T1_2_"+i);
compositeSample1.getChildrenT1().add(child1);
ChildT2 child2 = new ChildT2();
child2.setKey("childT1|"+i);
child2.setAttrA("T2A"+i);
child2.setAttrB("T2B"+i);
compositeSample1.getChildrenT2().add(child2);
}
dbContext.createCompositeSample1(compositeSample1);
where the code for createCompositeSample1 is:
public void createCompositeSample1(CompositeSample1 compositeSample1) {
if (createCompositeSample1 == null)
createCompositeSample1 = session.prepare("INSERT INTO " + DbContext.DATABASE_NAME + ".compositeSample1(id, childT1Key, childT2Key, rootAttr1) VALUES(?,?,?,?)");
if (createCompositeSample1ChildT1 == null)
createCompositeSample1ChildT1 = session.prepare("INSERT INTO " + DbContext.DATABASE_NAME + ".compositeSample1(id, childT1Key, childT2Key, childT1Attr1, childT1Attr2) VALUES(?,?,?,?,?)");
if (createCompositeSample1ChildT2 == null)
createCompositeSample1ChildT2 = session.prepare("INSERT INTO " + DbContext.DATABASE_NAME + ".compositeSample1(id, childT1Key, childT2Key, childT2AttrA, childT2AttrB) VALUES(?,?,?,?,?)");
BoundStatement boundStatementParent = new BoundStatement(createCompositeSample1);
getSession().execute(boundStatementParent.bind(UUID.fromString(compositeSample1.getId()), null, null, compositeSample1.getRootAttr1()));
BoundStatement boundStatementChildT1 = new BoundStatement(createCompositeSample1ChildT1);
for(ChildT1 childT1 : compositeSample1.getChildrenT1()){
getSession().execute(boundStatementChildT1.bind(UUID.fromString(compositeSample1.getId()), childT1.getKey(), null, childT1.getAttr1(), childT1.getAttr2()));
}
BoundStatement boundStatementChildT2 = new BoundStatement(createCompositeSample1ChildT2);
for(ChildT2 childT2 : compositeSample1.getChildrenT2()){
getSession().execute(boundStatementChildT2.bind(UUID.fromString(compositeSample1.getId()), null, childT2.getKey(), childT2.getAttrA(), childT2.getAttrB()));
}
}
But I'm getting the following error
com.datastax.driver.core.exceptions.InvalidQueryException: Invalid null value for clustering key part childt1key
My goals are:
To prove that I can insert the CompositeSample1 without any children
Add children to any of the 2 relationships to ongoing
Can someone pinpoint what I'm doing wrong or haven't understood properly?
Thank you
You can change your primary key part, there is no meaning of making composite partition key with only single column (in this case you have only id column as a part of composite partition key), create table as mention below.
session.execute("CREATE TABLE IF NOT EXISTS compositeSample1 (id uuid, rootAttr1 text, childT1Key text, childT1Attr1 text, , childT1Attr2 text "+
", childT2Key text, childT2AttrA text, childT2AttrB text, PRIMARY KEY (id, childT1Key, childT2Key))");
And you can insert an empty string instead of null values, like given below
BoundStatement boundStatementParent = new BoundStatement(createCompositeSample1);
session.execute(boundStatementParent.bind(UUID.fromString(compositeSample1.getId()), StringUtils.EMPTY, StringUtils.EMPTY,
compositeSample1.getRootAttr1()));
BoundStatement boundStatementChildT1 = new BoundStatement(createCompositeSample1ChildT1);
for (ChildT1 childT1 : compositeSample1.getChildrenT1())
{
session.execute(boundStatementChildT1.bind(UUID.fromString(compositeSample1.getId()), childT1.getKey(),
StringUtils.EMPTY, childT1.getAttr1(), childT1.getAttr2()));
}
BoundStatement boundStatementChildT2 = new BoundStatement(createCompositeSample1ChildT2);
for (ChildT2 childT2 : compositeSample1.getChildrenT2())
{
session.execute(boundStatementChildT2.bind(UUID.fromString(compositeSample1.getId()), StringUtils.EMPTY,
childT2.getKey(), childT2.getAttrA(), childT2.getAttrB()));
}
It should work for you.
Related
I am trying to sort my table's content on the backend side, so I am sending org.springframework.data.domain.Pageable object to controller. It arrives correctly, but at the repository I am getting org.hibernate.hql.internal.ast.InvalidPathException. Somehow the field name I would use for sorting gets an org. package name infront of the filed name.
The Pageable object logged in the controller:
Page request [number: 0, size 10, sort: referenzNumber: DESC]
Exception in repository:
Invalid path: 'org.referenzNumber'","logger_name":"org.hibernate.hql.internal.ast.ErrorTracker","thread_name":"http-nio-8080-exec-2","level":"ERROR","level_value":40000,"stack_trace":"org.hibernate.hql.internal.ast.InvalidPathException: Invalid path: 'org.referenzNumber'\n\tat org.hibernate.hql.internal.ast.util.LiteralProcessor.lookupConstant(LiteralProcessor.java:111)
My controller endpoint:
#GetMapping(value = "/get-orders", params = { "page", "size" }, produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<PagedModel<KryptoOrder>> getOrders(
#ApiParam(name = "searchrequest", required = true) #Validated final OrderSearchRequest orderSearchRequest,
#PageableDefault(size = 500) final Pageable pageable, final BindingResult bindingResult,
final PagedResourcesAssembler<OrderVo> pagedResourcesAssembler) {
if (bindingResult.hasErrors()) {
return ResponseEntity.badRequest().build();
}
PagedModel<Order> orderPage = PagedModel.empty();
try {
var orderVoPage = orderPort.processOrderSearch(resourceMapper.toOrderSearchRequestVo(orderSearchRequest), pageable);
orderPage = pagedResourcesAssembler.toModel(orderVoPage, orderAssembler);
} catch (MissingRequiredField m) {
log.warn(RESPONSE_MISSING_REQUIRED_FIELD, m);
return ResponseEntity.badRequest().build();
}
return ResponseEntity.ok(orderPage);
}
the repository:
#Repository
public interface OrderRepository extends JpaRepository<Order, UUID> {
static final String SEARCH_ORDER = "SELECT o" //
+ " FROM Order o " //
+ " WHERE (cast(:partnerernumber as org.hibernate.type.IntegerType) is null or o.tradeBasis.account.retailpartner.partnerbank.partnerernumber = :partnerernumber)"
+ " and (cast(:accountnumber as org.hibernate.type.BigDecimalType) is null or o.tradeBasis.account.accountnumber = :accountnumber)"
+ " and (cast(:orderReference as org.hibernate.type.LongType) is null or o.tradeBasis.referenceNumber = :orderReference)"
+ " and (cast(:orderReferenceExtern as org.hibernate.type.StringType) is null or o.tradeBasis.kundenreferenceExternesFrontend = :orderReferenceExtern)"
+ " and (cast(:dateFrom as org.hibernate.type.DateType) is null or o.tradeBasis.timestamp > :dateFrom) "
+ " and (cast(:dateTo as org.hibernate.type.DateType) is null or o.tradeBasis.timestamp < :dateTo) ";
#Query(SEARCH_ORDER)
Page<Order> searchOrder(#Param("partnerernumber") Integer partnerernumber,
#Param("accountnumber") BigDecimal accountnumber, #Param("orderReference") Long orderReference,
#Param("orderReferenceExtern") String orderReferenceExtern, #Param("dateFrom") LocalDateTime dateFrom,
#Param("dateTo") LocalDateTime dateTo, Pageable pageable);
}
Update:
I removed the parameters from the sql query, and put them back one by one to see where it goes sideways. It seems as soon as the dates are involved the wierd "org." appears too.
Update 2:
If I change cast(:dateTo as org.hibernate.type.DateType) to cast(:dateFrom as date) then it appends the filed name with date. instead of org..
Thanks in advance for the help
My guess is, Spring Data is confused by the query you are using and can't properly append the order by clause to it. I would recommend you to use a Specification instead for your various filters. That will not only improve the performance of your queries because the database can better optimize queries, but will also make use of the JPA Criteria API behind the scenes, which requires no work from Spring Data to apply an order by specification.
Since your entity Order is named as the order by clause of HQL/SQL, my guess is that Spring Data tries to do something stupid with the string to determine the alias of the root entity.
I have a table form_header with 3 records
There are more fields in the table decided not to add it here in the post since most are irrelevant. I created a class/entity to get the count with distinct for each status in sql.
#Entity()
#Table(name = "Form_Header")
#SqlResultSetMapping(name = "myMapping",
entities = {#EntityResult(
entityClass = FormSummary.class,
fields = {#FieldResult(name = "status", column = "status"),
#FieldResult(name = "id", column = "header_id")})})
public class FormSummary {
#Id()
private Long id;
private String status;
<getter and setter>
with entity manager
List<FormSummary> results = entityManager.createNativeQuery("select DISTINCT(status), COUNT(header_id) as header_id from Form_Header where is_deleted = 0 group by status order by status", "myMapping").getResultList();
for (FormSummary x : results) {
System.out.println("ABC " + x.getId());
System.out.println("ABC " + x.getStatus());
}
Issue is the sysout is showing this
instead of this
status header_id
APPROVE 1
DRAFT 1
SUBMITTED 1
Whats even weird is if I add an extra record in the table with the same status
I will get the correct data in my jpa
Am I missing something in my code or a possible bug with SqlResultSetMapping?
I have a parent which stores a list of children. When i update the children(add/edit/remove), is there a way to automatically decide which child to remove or edit based on the foreign key? Or do i have to manually check through all the child to see which are new or modified?
Parent Class
#Entity
#EntityListeners(PermitEntityListener.class)
public class Permit extends Identifiable {
#OneToMany(fetch = FetchType.LAZY, cascade=CascadeType.ALL, mappedBy = "permit")
private List<Coordinate> coordinates;
}
Child Class
#Entity
public class Coordinate extends Identifiable {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "permit_id", referencedColumnName = "id")
private Permit permit;
private double lat;
private double lon;
}
Parent's Controller
#PutMapping("")
public #ResponseBody ResponseEntity<?> update(#RequestBody Permit permit) {
logger.debug("update() with body {} of id {}", permit, permit.getId());
if (!repository.findById(permit.getId()).isPresent()) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}
Permit returnedEntity = repository.save(permit);
repository.flush();
return ResponseEntity.ok(returnedEntity);
}
=EDIT=
Controller Create
#Override
#PostMapping("")
public #ResponseBody ResponseEntity<?> create(#RequestBody Permit permit) {
logger.debug("create() with body {}", permit);
if (permit == null || permit.getId() != null) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}
List<Coordinate> coordinates = permit.getCoordinates();
if (coordinates != null) {
for (int x = 0; x < coordinates.size(); ++x) {
Coordinate coordinate = coordinates.get(x);
coordinate.setPermit(permit);
}
}
Permit returnedEntity = repository.save(permit);
repository.flush();
return ResponseEntity.ok(returnedEntity);
}
Controller Update
#PutMapping("")
public #ResponseBody ResponseEntity<?> update(#RequestBody Permit permit) {
logger.debug("update() with body {} of id {}", permit, permit.getId());
if (!repository.findById(permit.getId()).isPresent()) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}
List<Coordinate> repoCoordinate = coordinateRepository.findByPermitId(permit.getId());
List<Long> coordinateIds = new ArrayList<Long>();
for (Coordinate coordinate : permit.getCoordinates()) {
coordinate.setPermit(permit);
//if existing coordinate, save the ID in coordinateIds
if (coordinate.getId() != null) {
coordinateIds.add(coordinate.getId());
}
}
//loop through coordinate in repository to find which coordinate to remove
for (Coordinate coordinate : repoCoordinate) {
if (!(coordinateIds.contains(coordinate.getId()))) {
coordinateRepository.deleteById(coordinate.getId());
}
}
Permit returnedEntity = repository.save(permit);
repository.flush();
return ResponseEntity.ok(returnedEntity);
}
I have tested this and it is working, is there no simplified way of doing this?
You were close to the solution. The only thing you're missing is orphanRemoval=true on your one to many mapping:
#Entity
#EntityListeners(PermitEntityListener.class)
public class Permit extends Identifiable {
#OneToMany(mappedBy = "permit", cascade=CascadeType.ALL, orphanRemoval=true)
private List<Coordinate> coordinates;
}
Flagging the mapping for orphan removal will tell the underlying ORM to delete any entities that no longer belong to any parent entity. Since you removed a child element from the list, it will be deleted when you save the parent element.
Creating new elements and updating old is based on the CascadeType. Since you have CascadeType.ALL all elements in the list without an ID will be saved to the database and assigned a new ID when you save the parent entity, and all elements that are already in the list and have an ID will be updated.
On a side note, you might need to update the setter method for List coordinates to look something like:
public void setCoordinates(List<Coordinates> coordinates) {
this.coordinates = coordinates;
this.coordinates.forEach(coordinate -> coordinates.setPermit(this));
}
Or simply use #JsonManagedReference and #JsonBackReference if you're working with JSON.
I have a parent which stores a list of children.
Lets write the DDL for it.
TABLE parent (
id integer pk
)
TABLE child(
id integer pk
parent_id integer FOREIGN KEY (parent.id)
)
When i update the children(add/edit/remove), is there a way to automatically decide which child to remove or edit based on the foreign key?
Assuming you have a new child #5 bound to the parent #2 and:
The FK in the DDL is correctly
The entitys knows the FK
You are using the same jpa-context
The transaction is executed correctly
Then every call to parent.getChilds() must(!) return all the entitys that are existing before your transaction has been executed and the same instance of the entity that you have just committed to the database.
Then, if you remove child #5 of parent #2 and the transaction executed successfully parent.getChilds() must return all entitys without child #5.
Special case:
If you remove parent #2 and you have cascade-delete in the DDL as well as in the Java-Code all childrens must be removed from the Database as well as the parent #2 in the Database you just removed. In this case the parent #2 is not bound anymore to the jpa-context and all the childrens of parent #2 are not bound anymore to the jpa-context.
=Edit=
You could use merge. This will work for constructs like this:
POST {
"coordinates": [{
"lat":"51.33",
"lon":"22.44"
},{
"lat":"50.22",
"lon":"22.33"
}]
}
It will create one row in table "permit" and two rows in table "coordinate", both coordinates are bound to the permit-row. The result will include the ids set.
But: You will have to do the validation work (check that id is null, check that coordinates not refering different permit, ...)!
The removal of coordinates must be done using the DELETE method:
DELETE /permit/972/coordinate/3826648305
I have some data on employees which stores typical information like first and last name and dob, etc... The business case I have been given requires me to pull a record for an employee based on a criteria of first name, last name and dob.
At first I thought this would be easy, but after doing a search in db, I found the dob records have differing timestamps (which doesn't make sense). Without getting into the internal company issues of addressing this, I need my query to look at a dob with a start of day and end of day range.
My sample code below demonstrates my latest effort to solve this issue but have not been able to match results with what i see in the db. This code is using Spring Mongodb Query and mongoDbTemplate for constructing the queries. I could really use some help in figuring this one out.
I have to set a date range to ensure I get the employee for that day but can't figure out how to construct the query correctly.
sample method:
#Slf4j
#Repository
public class EmployeeDao {
private static final int START_OF_DAY = 0;
private static final int END_OF_DAY = 1;
#Getter private MongoTemplate mongoTemplate;
#Autowired
public void setMongoTemplate(MongoTemplate mongoTemplate) {
DB db = mongoTemplate.getDb();
mongoTemplate = new MongoTemplate(db.getMongo(), "EmployeeDB");
this.mongoTemplate = mongoTemplate;
}
public List<Employee> findEmployeeByNameIdDobRange(String lName, String fName, Date dob){
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
String startFmt = df.format(convertTo(dob, START_OF_DAY));
String endFmt = df.format(convertTo(dob, END_OF_DAY));
Query q = new Query();
q.addCriteria(Criteria.where("employee.firstName").regex(fName, "i")
.and("employee.lastName").regex(lName, "i")
.andOperator(
Criteria.where("employee.dob").gte(startFmt).lte(endFmt)
)
);
return return getMongoTemplate().find(q, Employee.class);
}
private Date convertTo(Date dt, int startOrEnd){
Calendar cal = Calendar.getInstance();
cal.setTime(dt);
switch (startOrEnd) {
case END_OF_DAY:
cal.set(Calendar.HOUR_OF_DAY, 23);
cal.set(Calendar.MINUTE, 59);
cal.set(Calendar.SECOND, 59);
cal.set(Calendar.MILLISECOND, 999);
break;
case START_OF_DAY:
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 1);
cal.set(Calendar.SECOND, 1);
cal.set(Calendar.MILLISECOND, 1);
break;
default:
throw new IllegalArgumentException("starOrEnd paramter does not equal 0 for beggining or 1 for end of day");
}
log.info(cal.toString());
return cal.getTime();
}
I appreciate any suggestions that can be made. Thanks!
I've made a utility method to get schema from a db table. In this case Oracle 11 db.
public static DataTable GetColumnsSchemaTable(DbConnection cnctn, string tableName)
{
DataTable schemaTable;
string[] restrictions = new string[3] { null, tableName, null };
schemaTable = cnctn.GetSchema("Columns", restrictions);
/* table name is case sensitive and in XXXX db table names are UPPER */
if (schemaTable.Rows.Count == 0)
{
restrictions = new string[3] { null, tableName.ToUpper(), null };
schemaTable = cnctn.GetSchema("Columns", restrictions);
}
return schemaTable;
}
This works fine when the cnctn is created with System.Data.OracleClient provider factory. When it's created with System.Data.OleDb provider factory the table has no rows. I have an other utility method to get connection strings:
public static string GetDbConnectionString(DbConnection cnnctn, string provider, string server, string dbName, string user, string pwd)
{
if (cnnctn is OleDbConnection)
{
string usedProvider;
if (provider == null)
usedProvider = "msdaora";
else
usedProvider = provider;
return string.Format("Provider={0};Data Source={1};User Id={2};Password={3};",
usedProvider, dbName, user, pwd);
}
else if (cnnctn is System.Data.OracleClient.OracleConnection)
{
return string.Format("Data Source={0};User Id={1};Password={2};",
dbName, user, pwd);
}
else if (cnnctn is Oracle.DataAccess.Client.OracleConnection)
{
return string.Format("Data Source={0};User Id={1};Password={2};",
dbName, user, pwd);
}
else if (cnnctn is SqlConnection)
{
return string.Format("Data Source={0}; Initial Catalog={1}; User Id={2}; Password={3};",
server, dbName, user, pwd);
}
return string.Empty;
}
and the db connection works (i'm deleting rows before trying to get schema). All help will be appreciated.
Thanks & Best Regards - Matti
Ok. I sorted it out. I made this code long time ago only for now deprecated OracleClient and left the possibility to use other connections / provider factories. I didn't remember anymore that the restrictions vary from connection to connection. So the correct usage is:
string[] restrictions = new string[4] { null, null, tableName, null };
for OleDbConnection.