I'm following the Cassandra java object mapper tutorial on Datastax website.
and while defining accessors
#Query("SELECT * FROM testks.user ")
Result<User> getAll();
running this query give me a
com.datastax.driver.core.exceptions.InvalidQueryException: Some partition key parts are missing: id
Looking around it seems that you cannot query in cassandra without providing a partition key. Is that the case? This seems like a strange requirement. If I want a select all query, how would I go about doing that?
the table is defined as
CREATE TABLE testks.user (
id text PRIMARY KEY,
name text
)
You didn't provide many details. if you follow these below steps you shouldn't get any error
You should define the user model like below
User.java
#Table(name = "user")
public class User {
#PartitionKey
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "User{" + "id=" + id + ", name=" + name + '}';
}
}
And Repository like below
UserAccessor.java
#Accessor
public interface UserAccessor {
#Query("SELECT * FROM user")
Result<User> getAll();
}
Here is how you can use the repository
Main.Java
public static void main(String[] args) {
try (Cluster cluster = Cluster.builder().addContactPoints("127.0.0.1").withCredentials("cassandra", "cassandra").build(); Session session = cluster.connect("test")) {
MappingManager manager = new MappingManager(session);
UserAccessor userAccessor = manager.createAccessor(UserAccessor.class);
System.out.println(userAccessor.getAll().all());
}
}
Related
GoodEvening
i'm new about mongo, and i choose to use a mix & match of RepositoryPattern and Mongotemplate
Now i find a very strange error when i want to use MongoTemplate to retrive a specific value of an inner object in order to calculate a progressive ID
It seems my code work for 0 or 1 document inside a collection, but when i have equal or more than 2 documents, the method will throw a
org.springframework.dao.IncorrectResultSizeDataAccessException: Query { "$java" : Query: {}, Fields: {}, Sort: {} } returned non unique result.
as if limit dosen't work.
I have just, for now, a rest and a repository pattern, but in the service layer i have another autowire whit mongotemplate class, this template is used by just 1 method.
public DetailedOrder findTheBiggestBy(String byWhat){
Query query = new Query();
query.with(Sort.by(Sort.Direction.DESC,byWhat)).limit(1);
return mongoDb.findOne(query,DetailedOrder.class);
}
As you can see is pretty simple and it actually work, becouse before moving the Template inside Service it actually work whit various documents inside collection, when i autowired it over the rest layer.
There is something i miss and is not rly related to mongo but related to spring autowire?
My detailorderClass is
package com.service.backend.BK.Pojo;
import com.service.backend.BK.Constants.Constant;
import org.bson.codecs.pojo.annotations.BsonId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.FieldType;
import org.springframework.data.mongodb.core.mapping.MongoId;
import java.util.List;
#Document("Orders")
public class DetailedOrder {
#MongoId(FieldType.OBJECT_ID)
private String id;
#Field
private List<Integer> category;
// 1 junk to 10 Pristine
#Field
private Integer quality;
#Field
private BaseOrder baseOrder;
#Field
private String qualityDescription;
public DetailedOrder(){}
public DetailedOrder(String Description,Double price, List<Integer> category, Integer quality) {
this.category = category;
this.quality = quality;
this.qualityDescription = qualityDescriptionPairFactory(quality);
this.baseOrder=new BaseOrder(Description,price);
}
public List<Integer> getCategory() {
return category;
}
public void setCategory(List<Integer> category) {
this.category = category;
}
public Integer getQuality() {
return quality;
}
public void setQuality(Integer quality) {
this.quality = quality;
}
public String getQualityDescription() {
return qualityDescription;
}
public void setQualityDescription(String qualityDescription) {
this.qualityDescription = qualityDescription;}
private String qualityDescriptionPairFactory(int quality){
switch (quality){
case 1:return Constant.Quality.NOGRAD.label;
case 2:return Constant.Quality.HEAVYDMN.label;
case 3:return Constant.Quality.LOOSE.label;
case 4:return Constant.Quality.POOR.label;
case 5:return Constant.Quality.LIGHTDMN.label;
case 6:return Constant.Quality.GOOD.label;
case 7:return Constant.Quality.EXCELENT.label;
case 8:return Constant.Quality.NEARMINT.label;
case 9:return Constant.Quality.MINT.label;
case 10:return Constant.Quality.NEWUNRL.label;
default:return Constant.Quality.NOGRAD.label; }
}
#Override
public String toString() {
return "DetailedOrder{" +
"id='" + id + '\'' +
", baseOrder='" + getBaseOrder().returnBaseOrder() + '\'' +
", category=" + category +
", quality=" + quality +
", qualityDescription='" + qualityDescription + '\'' +
'}';
}
public BaseOrder getBaseOrder() {
return baseOrder;
}
public void setBaseOrder(BaseOrder baseOrder) {
this.baseOrder = baseOrder;
}
public void setId(String id) {
this.id = id;
}
}
and that lead to baseOrderClass
package com.service.backend.BK.Pojo;
import org.joda.time.DateTime;
import org.springframework.data.mongodb.core.mapping.Document;
public class BaseOrder {
private String id;
private String description;
private Double desideredPrice;
//Pending,active,Rejected,Hault,etc etc
private int status;
protected BaseOrder(){}
protected BaseOrder(String description, Double desideredPrice) {
this.description = description;
this.desideredPrice = desideredPrice;
}
public String returnBaseOrder(){
return "BaseOrder{" +
"id='" + id + '\'' +
", description='" + description + '\'' +
", desideredPrice=" + desideredPrice +
", status=" + status +
'}';
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Double getDesideredPrice() {
return desideredPrice;
}
public void setDesideredPrice(Double desideredPrice) {
this.desideredPrice = desideredPrice;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
//###############################################################################################################//
}
The OPs question related to a spring context issue. By just accessing the repository directly the problem could be resolved.
Within the comments an additional question/target has been found, i would like to provide an answer for that.
How to have persistence access within the domain layer?
I have created a repository at GitHub where you can find a working solution. This code is not best practice but will provide a solution that works.
Every constructed domain object needs access to the declared repositories. That can be a problem when using dependency injection. In order to keep entity construction pristine an abstract class has been introduces that takes the task to get the concrete repositories.
The core idea is to make the Spring ApplicationContext accessable statically. Having this context an access to the repositories is just one call away.
Beans having the ApplicationContextAware interface will be called in a very early stage of Spring Boots startup. When using this class to access the context every consumer has to call it after Spring has been loaded. This can be achieved by using #Component, #Configuration, #Bean to run code and there will be no race condition.
Further details are within the repositories readme file.
I hope that this helps you :)
#Transactionalshould itself reflect the changes made to the entity in the database.
I'm creating an application where the client can create a Car entity that looks like this (the update method is later used by PUT, do not pay attention to the brand property):
#Entity
#Table(name = "cars")
public class Car {
#Id
#GeneratedValue(generator = "inc")
#GenericGenerator(name = "inc", strategy = "increment")
private int id;
#NotBlank(message = "car name`s must be not empty")
private String name;
private LocalDateTime productionYear;
private boolean tested;
public Car() {
}
public Car(#NotBlank(message = "car name`s must be not empty") String name, LocalDateTime productionYear) {
this.name = name;
this.productionYear = productionYear;
}
#ManyToOne
#JoinColumn(name = "brand_id")
private Brand brand;
public int getId() {
return id;
}
void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public LocalDateTime getProductionYear() {
return productionYear;
}
public void setProductionYear(LocalDateTime productionYear) {
this.productionYear = productionYear;
}
public boolean isTested() {
return tested;
}
public void setTested(boolean tested) {
this.tested = tested;
}
public Brand getBrand() {
return brand;
}
void setBrand(Brand brand) {
this.brand = brand;
}
public Car update(final Car source) {
this.productionYear = source.productionYear;
this.brand = source.brand;
this.tested = source.tested;
this.name = source.name;
return this;
}
}
In my application, the client can create a new Car or update an existing one with the PUT method.
My controller:
#RestController
public class CarController {
private Logger logger = LoggerFactory.getLogger(CarController.class);
private CarRepository repository;
public CarController(CarRepository repository) {
this.repository = repository;
}
//The client can create a new resource or update an existing one via PUT
#Transactional
#PutMapping("/cars/{id}")
ResponseEntity<?> updateCar(#PathVariable int id, #Valid #RequestBody Car source) {
//update
if(repository.existsById(id)) {
repository.findById(id).ifPresent(car -> {
car.update(source); //it doesn`t work
//Snippet below works
//var updated = car.update(source);
//repository.save(updated);
});
return ResponseEntity.noContent().build();
}
//create
else {
var result = repository.save(source);
return ResponseEntity.created(URI.create("/" + id)).body(result);
}
}
}
When I create a new Car, it works. However as described in the code, when there is no save method the entity is not changed although I get the status 204 (no content). When there is a save method, it works fine.
Do you know why this is so?
One of the users asked me for a Brand entity. I haven't created any Brand object so far but essentially Car can belong to a specific Brand in my app. So far, no Car belongs to any Brand. Here is this entity:
#Entity
#Table(name = "brands")
public class Brand {
#Id
#GeneratedValue(generator = "i")
#GenericGenerator(name = "i", strategy = "increment")
private int id;
#NotBlank(message = "brand name`s must be not empty")
private String name;
private LocalDateTime productionBrandYear;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "brand")
private Set<Car> cars;
#ManyToOne
#JoinColumn(name = "factory_id")
private Factory factory;
public Brand() {
}
public int getId() {
return id;
}
void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public LocalDateTime getProductionBrandYear() {
return productionBrandYear;
}
public void setProductionBrandYear(LocalDateTime productionBrandYear) {
this.productionBrandYear = productionBrandYear;
}
public Set<Car> getCars() {
return cars;
}
public void setCars(Set<Car> cars) {
this.cars = cars;
}
public Factory getFactory() {
return factory;
}
public void setFactory(Factory factory) {
this.factory = factory;
}
}
I tried your entities with same use case locally and found out everything is working fine, I am writing here my findings and configurations so that you can verify what's going on wrong for you.
So, when I issue a PUT call providing id but Car entity doesn't exist into table, it gets created and I receive 201 response (I guess you are getting the same)
you can see that row with value got inserted into table as well
and these are the query logs printed
- [nio-8080-exec-8] org.hibernate.SQL: select count(*) as col_0_0_ from car car0_ where car0_.id=?
[nio-8080-exec-8] org.hibernate.SQL: select car0_.id as id1_1_0_, car0_.brand_id as brand_id5_1_0_, car0_.name as name2_1_0_, car0_.production_year as producti3_1_0_, car0_.tested as tested4_1_0_ from car car0_ where car0_.id=?
[nio-8080-exec-8] org.hibernate.SQL: insert into car (brand_id, name, production_year, tested) values (?, ?, ?, ?)
Now, let's come to updating the same entity, when issued PUT request for same id with changed values notice that values changes in table and update queries in log
You can see that got same 204 response with empty body, let's look the table entry
So changes got reflected in DB, let's look at the SQL logs for this operation
select count(*) as col_0_0_ from car car0_ where car0_.id=?
[nio-8080-exec-1] org.hibernate.SQL: select car0_.id as id1_1_0_, car0_.brand_id as brand_id5_1_0_, car0_.name as name2_1_0_, car0_.production_year as producti3_1_0_, car0_.tested as tested4_1_0_, brand1_.id as id1_0_1_, brand1_.name as name2_0_1_, brand1_.production_year as producti3_0_1_ from car car0_ left outer join brand brand1_ on car0_.brand_id=brand1_.id where car0_.id=?
[nio-8080-exec-1] org.hibernate.SQL: update car set brand_id=?, name=?, production_year=?, tested=? where id=?
So, I am not sure, how you verified and what you verified but your entities must work, I have used same controller function as yours
#RestController
class CarController {
private final CarRepository repository;
public CarController(CarRepository repository) {
this.repository = repository;
}
#PutMapping("/car/{id}")
#Transactional
public ResponseEntity<?> updateCar(#PathVariable Integer id, #RequestBody Car source) {
if(repository.existsById(id)) {
repository.findById(id).ifPresent(car -> car.update(source));
return ResponseEntity.noContent().build();
}else {
Car created = repository.save(source);
return ResponseEntity.created(URI.create("/" + created.getId())).body(created);
}
}
}
Possible differences from your source code could be as follow:
I used IDENTITY generator to generate the PRIMARY KEY, instead of the one you have on your entity as it was easy for me to test.
I provided ObjectMapper bean to serialize/deserialize the request body to Car object to support Java 8 LocalDateTime conversion, you may have your way to send datetime values, so that it converts to Car Object.
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
// And Object mapper bean
#Bean
public static ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
return mapper;
}
However, these differences should not matter.
application.properties
To print query logs to verify if queries are fired or not
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.username=test
spring.datasource.password=test
spring.datasource.jpa.show-sql=true
spring.jpa.open-in-view=false
logging.level.org.hibernate.SQL=DEBUG
The fact that you are updating the car object doesn't mean it updates the value in the DB. You always need to call repository.save() method to persist your changes in the DB.
Im using loacalstack with dynamodb setup. Iv a table with two columns, an Id and a name column. Im struggling to query the table by 'name' using DynamoDBMapper. Below is a snippet of my setup
entity >
#AllArgsConstructor
#NoArgsConstructor
#DynamoDBTable(tableName = "my-table")
public class Table {
private String id;
private String name;
#DynamoDBHashKey(attributeName = "id")
#DynamoDBAutoGeneratedKey
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
#DynamoDBAttribute
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#query >
public Table getByName(String name) {
return dynamoDBMapper.load(Table.class, name);
}
aws dynamodb create-table --endpoint-url=http://localstack:4569 --table-name my-table \
--attribute-definitions AttributeName=id,AttributeType=S AttributeName=name,AttributeType=S \
--key-schema AttributeName=id,KeyType=HASH AttributeName=name,KeyType=RANGE \
Any help appreciated
Obviously you cannot query the table by name as it is not the partition/hash key. Either you can use scan or you should state name as a GSI field and then query it. Scan command would be like:
dynamoDBMapper.scan(Table.class, new DynamoDBScanExpression());
The above command will scan the entire table. Read about DynamoDBScanExpression here to see how you can filter it with a specific name.
You can add a filter like on name like:
public Table getByName(String name) {
DynamoDBScanExpression scanExpression = new DynamoDBScanExpression();
scanExpression.addFilterCondition("name", new Condition()
.withComparisonOperator(ComparisonOperator.EQ)
.withAttributeValueList(new AttributeValue().withS(name)));
dynamoDBMapper.scan(Table.class, scanExpression);
}
I have small problem to create correctly mathod adding people to teams. The reason is my problem is that i can't add two arguments in requestbody (or PathVariable).
I want to take a person id and team id and return both connected. I create separated class where i generate id but i have problem to imput this correctly id from database:
This is my method in cotroller:
#PostMapping("/addPeopleToTeams/{teamId}/{personId}")
#ResponseBody
public String addPeopleToTeam(#RequestBody TeamsAndPersonsId teamsAndPersonsId){
System.out.println(" " + teamsAndPersonsId.getPersonId());
System.out.println(" " + teamsAndPersonsId.getTeamId());
teamService.findTeamById(teamsAndPersonsId.getTeamId());
personService.findById(teamsAndPersonsId.getPersonId());
return teamsAndPersonsId.getTeamId() + " " + teamsAndPersonsId.getPersonId();
}
This is my method find by id in service:
public Optional<TeamDto> findTeamById(Long id) {
Assert.notNull(id, "ID must exist ");
return teamRepository
.findById(id)
.map(p -> modelMapper.map(p, TeamDto.class));
}
Other my class is Team - this is entity, teamDto, and TeamsAndPersonsId.
TeamsAndPersonsId class:
public class TeamsAndPersonsId {
private Long personId;
private Long teamId;
public TeamsAndPersonsId(Long personId, Long teamId) {
this.personId = personId;
this.teamId = teamId;
}
public TeamsAndPersonsId(){}
public Long getPersonId() {
return personId;
}
public void setPersonId(Long personId) {
this.personId = personId;
}
public Long getTeamId() {
return teamId;
}
public void setTeamId(Long teamId) {
this.teamId = teamId;
}
How to correctly wrote this controller method to return real team and persons id? Like i add in surce:
#PostMapping("/addPeopleToTeams/{teamId}/{personId}"
You can actually use multiple path variables, use them to create a TeamsAndPersonsId , then keep the rest of your current code :
#PostMapping("/addPeopleToTeams/{teamId}/{personId}")
#ResponseBody
public String addPeopleToTeam(#PathVariable Long teamId, #PathVariable Long personId){
TeamsAndPersonsId teamsAndPersonsId = new TeamsAndPersonsId(teamId, personId);
// etc...
Can you provide an example for mapping collections using datastax api annotations to use Map.
class pojo {
#PartitionKey(value = 0)
#Column(name = "user_id")
String userId;
#Column(name = "attributes")
// How to map it
Map<String, String> attributes;
}
error log :
2015-08-03 16:33:34,568 INFO com.jpma.jpmc.slot.persistance.DAOFactory main - Cassandra Cluster Details: ConnectionCfg [userName=test, password=test, port=9042, seeds=[Ljava.lang.String;#1a85bd75, keySpace=test]
java.lang.Class
2015-08-03 16:33:34,646 DEBUG com.datastax.driver.mapping.EntityMapper main - Preparing query INSERT INTO "test"."user_event_date"("user_id","entry_date","entry_time","app","attributes","user_ip","user_authschemes") VALUES (?,?,?,?,?,?,?);
com.datastax.driver.core.exceptions.InvalidQueryException: Unknown identifier attributes
Based on the error message you are seeing, I'm guessing that attributes is not defined in your table definition. Would you mind editing your post with that?
But when I build my CQL table like this (note the compound partition key of itemid and version):
CREATE TABLE products.itemmaster (
itemid text,
version int,
productid uuid,
supplierskumap map<uuid, text>,
PRIMARY KEY ((itemid,version), productid)
);
...insert this row:
INSERT INTO products.itemmaster (itemid,version,productid,supplierskumap)
VALUES ('item1',1,26893749-dcfc-42c7-892c-bee8c9cff630,
{1351f82f-5dc5-4328-82f4-962429c92a2b:'86CCG123'});
...and I build my POJO like this:
#Table(keyspace = "products", name = "itemmaster")
public class Product {
#PartitionKey(0)
private String itemid;
#PartitionKey(1)
private int version;
#ClusteringColumn
private UUID productid;
#Column(name="supplierskumap")
private Map<UUID,String> suppliersku;
public UUID getProductid() {
return productid;
}
public void setProductid(UUID _productid) {
this.productid = _productid;
}
public int getVersion() {
return this.version;
}
public void setVersion(int _version)
{
this.version = _version;
}
public String getItemid() {
return itemid;
}
public void setItemid(String _itemid) {
this.itemid = _itemid;
}
public Map<UUID, String> getSuppliersku() {
return suppliersku;
}
public void setSuppliersku(Map<UUID, String> _suppliersku) {
this.suppliersku = _suppliersku;
}
}
...with this constructor and getProd method on my data access object (dao):
public ProductsDAO()
{
session = connect(CASSANDRA_NODES, USERNAME, PASSWORD);
prodMapper = new MappingManager(session).mapper(Product.class);
}
public Product getProd(String itemid, int version, UUID productid) {
return prodMapper.get(itemid,version,sku);
}
...then this main class successfully queries my table and maps my Map:
private static void main(String[] args) {
ProductsDAO dao = new ProductsDAO();
Product prod = dao.getProd("item1", 1, UUID.fromString("26893749-dcfc-42c7-892c-bee8c9cff630"));
System.out.println(
prod.getProductid() + " - " +
prod.getItemid() + " - " +
prod.getSuppliersku().get(UUID.fromString("1351f82f-5dc5-4328-82f4-962429c92a2b")));
dao.closeCassandra();
}
...and produces this output:
26893749-dcfc-42c7-892c-bee8c9cff630 - item1 - 86CCG123
NOTE: Edit made to the above example to support a compound partition key.