Why Spring is turning my object into an array of attributes? [duplicate] - arrays

I'm developing a Spring Boot application with Spring Data JPA. I'm using a custom JPQL query to group by some field and get the count. Following is my repository method.
#Query(value = "select count(v) as cnt, v.answer from Survey v group by v.answer")
public List<?> findSurveyCount();
It's working and result is obtained as follows:
[
[1, "a1"],
[2, "a2"]
]
I would like to get something like this:
[
{ "cnt":1, "answer":"a1" },
{ "cnt":2, "answer":"a2" }
]
How can I achieve this?

Solution for JPQL queries
This is supported for JPQL queries within the JPA specification.
Step 1: Declare a simple bean class
package com.path.to;
public class SurveyAnswerStatistics {
private String answer;
private Long cnt;
public SurveyAnswerStatistics(String answer, Long cnt) {
this.answer = answer;
this.count = cnt;
}
}
Step 2: Return bean instances from the repository method
public interface SurveyRepository extends CrudRepository<Survey, Long> {
#Query("SELECT " +
" new com.path.to.SurveyAnswerStatistics(v.answer, COUNT(v)) " +
"FROM " +
" Survey v " +
"GROUP BY " +
" v.answer")
List<SurveyAnswerStatistics> findSurveyCount();
}
Important notes
Make sure to provide the fully-qualified path to the bean class, including the package name. For example, if the bean class is called MyBean and it is in package com.path.to, the fully-qualified path to the bean will be com.path.to.MyBean. Simply providing MyBean will not work (unless the bean class is in the default package).
Make sure to call the bean class constructor using the new keyword. SELECT new com.path.to.MyBean(...) will work, whereas SELECT com.path.to.MyBean(...) will not.
Make sure to pass attributes in exactly the same order as that expected in the bean constructor. Attempting to pass attributes in a different order will lead to an exception.
Make sure the query is a valid JPA query, that is, it is not a native query. #Query("SELECT ..."), or #Query(value = "SELECT ..."), or #Query(value = "SELECT ...", nativeQuery = false) will work, whereas #Query(value = "SELECT ...", nativeQuery = true) will not work. This is because native queries are passed without modifications to the JPA provider, and are executed against the underlying RDBMS as such. Since new and com.path.to.MyBean are not valid SQL keywords, the RDBMS then throws an exception.
Solution for native queries
As noted above, the new ... syntax is a JPA-supported mechanism and works with all JPA providers. However, if the query itself is not a JPA query, that is, it is a native query, the new ... syntax will not work as the query is passed on directly to the underlying RDBMS, which does not understand the new keyword since it is not part of the SQL standard.
In situations like these, bean classes need to be replaced with Spring Data Projection interfaces.
Step 1: Declare a projection interface
package com.path.to;
public interface SurveyAnswerStatistics {
String getAnswer();
int getCnt();
}
Step 2: Return projected properties from the query
public interface SurveyRepository extends CrudRepository<Survey, Long> {
#Query(nativeQuery = true, value =
"SELECT " +
" v.answer AS answer, COUNT(v) AS cnt " +
"FROM " +
" Survey v " +
"GROUP BY " +
" v.answer")
List<SurveyAnswerStatistics> findSurveyCount();
}
Use the SQL AS keyword to map result fields to projection properties for unambiguous mapping.

This SQL query return List< Object[] > would.
You can do it this way:
#RestController
#RequestMapping("/survey")
public class SurveyController {
#Autowired
private SurveyRepository surveyRepository;
#RequestMapping(value = "/find", method = RequestMethod.GET)
public Map<Long,String> findSurvey(){
List<Object[]> result = surveyRepository.findSurveyCount();
Map<Long,String> map = null;
if(result != null && !result.isEmpty()){
map = new HashMap<Long,String>();
for (Object[] object : result) {
map.put(((Long)object[0]),object[1]);
}
}
return map;
}
}

I know this is an old question and it has already been answered, but here's another approach:
#Query("select new map(count(v) as cnt, v.answer) from Survey v group by v.answer")
public List<?> findSurveyCount();

define a custom pojo class say sureveyQueryAnalytics and store the query returned value in your custom pojo class
#Query(value = "select new com.xxx.xxx.class.SureveyQueryAnalytics(s.answer, count(sv)) from Survey s group by s.answer")
List<SureveyQueryAnalytics> calculateSurveyCount();

I do not like java type names in query strings and handle it with a specific constructor.
Spring JPA implicitly calls constructor with query result in HashMap parameter:
#Getter
public class SurveyAnswerStatistics {
public static final String PROP_ANSWER = "answer";
public static final String PROP_CNT = "cnt";
private String answer;
private Long cnt;
public SurveyAnswerStatistics(HashMap<String, Object> values) {
this.answer = (String) values.get(PROP_ANSWER);
this.count = (Long) values.get(PROP_CNT);
}
}
#Query("SELECT v.answer as "+PROP_ANSWER+", count(v) as "+PROP_CNT+" FROM Survey v GROUP BY v.answer")
List<SurveyAnswerStatistics> findSurveyCount();
Code needs Lombok for resolving #Getter

#Repository
public interface ExpenseRepo extends JpaRepository<Expense,Long> {
List<Expense> findByCategoryId(Long categoryId);
#Query(value = "select category.name,SUM(expense.amount) from expense JOIN category ON expense.category_id=category.id GROUP BY expense.category_id",nativeQuery = true)
List<?> getAmountByCategory();
}
The above code worked for me.

I used custom DTO (interface) to map a native query to - the most flexible approach and refactoring-safe.
The problem I had with this - that surprisingly, the order of fields in the interface and the columns in the query matters. I got it working by ordering interface getters alphabetically and then ordering the columns in the query the same way.

I just solved this problem :
Class-based Projections doesn't work with query native(#Query(value = "SELECT ...", nativeQuery = true)) so I recommend to define custom DTO using interface .
Before using DTO should verify the query syntatically correct or not

Get data with column name and its values (in key-value pair) using JDBC:
/*Template class with a basic set of JDBC operations, allowing the use
of named parameters rather than traditional '?' placeholders.
This class delegates to a wrapped {#link #getJdbcOperations() JdbcTemplate}
once the substitution from named parameters to JDBC style '?' placeholders is
done at execution time. It also allows for expanding a {#link java.util.List}
of values to the appropriate number of placeholders.
The underlying {#link org.springframework.jdbc.core.JdbcTemplate} is
exposed to allow for convenient access to the traditional
{#link org.springframework.jdbc.core.JdbcTemplate} methods.*/
#Autowired
protected NamedParameterJdbcTemplate jdbc;
#GetMapping("/showDataUsingQuery/{Query}")
public List<Map<String,Object>> ShowColumNameAndValue(#PathVariable("Query")String Query) throws SQLException {
/* MapSqlParameterSource class is intended for passing in a simple Map of parameter values
to the methods of the {#link NamedParameterJdbcTemplate} class*/
MapSqlParameterSource msp = new MapSqlParameterSource();
// this query used for show column name and columnvalues....
List<Map<String,Object>> css = jdbc.queryForList(Query,msp);
return css;
}

//in Service
`
public List<DevicesPerCustomer> findDevicesPerCustomer() {
LOGGER.info(TAG_NAME + " :: inside findDevicesPerCustomer : ");
List<Object[]> list = iDeviceRegistrationRepo.findDevicesPerCustomer();
List<DevicesPerCustomer> out = new ArrayList<>();
if (list != null && !list.isEmpty()) {
DevicesPerCustomer mDevicesPerCustomer = null;
for (Object[] object : list) {
mDevicesPerCustomer = new DevicesPerCustomer();
mDevicesPerCustomer.setCustomerId(object[0].toString());
mDevicesPerCustomer.setCount(Integer.parseInt(object[1].toString()));
out.add(mDevicesPerCustomer);
}
}
return out;
}`
//In Repo
` #Query(value = "SELECT d.customerId,count(*) FROM senseer.DEVICE_REGISTRATION d where d.customerId is not null group by d.customerId", nativeQuery=true)
List<Object[]> findDevicesPerCustomer();`

Related

Reading in all docs (doc id only if possible) from Solr without searching

I know Solr is meant to be used for searching.
However, I am doing some benchmarking and I wonder if there is a way to retrieve doc id of every document indexed.
The best option is retrieving without searching (if there exist a way).
I guess the alternative is to query all documents but only asks for doc id.
I will be using SolrJ, so operations of SolrJ would be useful
Use the /export end point: Exporting result sets.
It supports using the same fl parameter as regular search (although searching for just *:* will probably behave quite similar when you're using SolrJ).
In SolrJ you'll have to use the CloudSolrStream class instead to properly stream the results (as compared to the regular behavior when searching for *:*).
From Joel Bernstein's example when introducing the feature:
import org.apache.solr.client.solrj.io.*;
import java.util.*;
public class StreamingClient {
public static void main(String args[]) throws IOException {
String zkHost = args[0];
String collection = args[1];
Map props = new HashMap();
props.put("q", "*:*");
props.put("qt", "/export");
props.put("sort", "fieldA asc");
props.put("fl", "fieldA,fieldB,fieldC");
CloudSolrStream cstream = new CloudSolrStream(zkHost,
collection,
props);
try {
cstream.open();
while(true) {
Tuple tuple = cstream.read();
if(tuple.EOF) {
break;
}
String fieldA = tuple.getString("fieldA");
String fieldB = tuple.getString("fieldB");
String fieldC = tuple.getString("fieldC");
System.out.println(fieldA + ", " + fieldB + ", " + fieldC);
}
} finally {
cstream.close();
}
}
}

JPA2 CriteriaBuilder: Using LOB property for greaterThan comparison

My application is using SQLServer and JPA2 in the backend. App makes use of a timestamp column (in the SQLServer sense, which is equivalent to row version see here) per entity to keep track of freshly modified entities. NB SQLServer stores this column as binary(8).
Each entity has a respective timestamp property, mapped as #Lob, which is the way to go for binary columns:
#Lob
#Column(columnDefinition="timestamp", insertable=false, updatable=false)
public byte[] getTimestamp() {
...
The server sends incremental updates to mobile clients along with the latest database timestamp. The mobile client will then pass the old timestamp back to the server on the next refresh request so that the server knows to return only fresh data. Here's what a typical query (in JPQL) looks like:
select v from Visit v where v.timestamp > :oldTimestamp
Please note that I'm using a byte array as a query parameter and it works fine when implemented in JPQL this way.
My problems begin when trying to do the same using the Criteria API:
private void getFreshVisits(byte[] oldVersion) {
EntityManager em = getEntityManager();
CriteriaQuery<Visit> cq = cb.createQuery(Visit.class);
Root<Visit> root = cq.from(Visit.class);
Predicate tsPred = cb.gt(root.get("timestamp").as(byte[].class), oldVersion); // compiler error
cq.where(tsPred);
...
}
The above will result in compiler error as it requires that the gt method used strictly with Number. One could instead use the greaterThan method which simply requires the params to be Comparable and that would result in yet another compiler error.
So to sum it up, my question is: how can I use the criteria api to add a greaterThan predicate for a byte[] property? Any help will be greatly appreciated.
PS. As to why I'm not using a regular DateTime last_modified column: because of concurrency and the way synchronization is implemented, this approach could result in lost updates. Microsoft's Sync Framework documentation recommends the former approach as well.
I know this was asked a couple of years back but just in case anyone else stumbles upon this.. In order to use a SQLServer rowver column within JPA you need to do a couple of things..
Create a type that will wrap the rowver/timestamp:
import com.fasterxml.jackson.annotation.JsonIgnore;
import javax.xml.bind.annotation.XmlTransient;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.Arrays;
/**
* A RowVersion object
*/
public class RowVersion implements Serializable, Comparable<RowVersion> {
#XmlTransient
#JsonIgnore
private byte[] rowver;
public RowVersion() {
}
public RowVersion(byte[] internal) {
this.rowver = internal;
}
#XmlTransient
#JsonIgnore
public byte[] getRowver() {
return rowver;
}
public void setRowver(byte[] rowver) {
this.rowver = rowver;
}
#Override
public int compareTo(RowVersion o) {
return new BigInteger(1, rowver).compareTo(new BigInteger(1, o.getRowver()));
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RowVersion that = (RowVersion) o;
return Arrays.equals(rowver, that.rowver);
}
#Override
public int hashCode() {
return Arrays.hashCode(rowver);
}
}
The key here is that it implement Comparable if you want to use it in calculations (which you definitely do)..
Next create a AttributeConverter that will move from a byte[] to the class you just made:
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
/**
* JPA converter for the RowVersion type
*/
#Converter
public class RowVersionTypeConverter implements AttributeConverter<RowVersion, byte[]> {
#Override
public byte[] convertToDatabaseColumn(RowVersion attribute) {
return attribute != null ? attribute.getRowver() : null;
}
#Override
public RowVersion convertToEntityAttribute(byte[] dbData) {
return new RowVersion(dbData);
}
}
Now let's apply this RowVersion attribute/type to a real world scenario. Let's say you wanted to find all Programs that have changed on or before some point in time.
One straightforward way to solve this would be to use a DateTime field in the object and timestamp column within db. Then you would use 'where lastUpdatedDate <= :date'.
Suppose that you don't have that timestamp column or there's no guarantee that it will be updated properly when changes are made; or let's say your shop loves SQLServer and wants to use rowver instead.
What to do? There are two issues to solve.. one how to generate a rowver and two is how to use the generated rowver to find Programs.
Since the database generates the rowver, you can either ask the db for the 'current max rowver' (a custom sql server thing) or you can simply save an object that has a RowVersion attribute and then use that object's generated RowVersion as the boundary for the query to find the Programs changed after that time. The latter solution is more portable is what the solution is below.
The SyncPoint class snippet below is the object that is used as a 'point in time' kind of deal. So once a SyncPoint is saved, the RowVersion attached to it is the db version at the time it was saved.
Here is the SyncPoint snippet. Notice the annotation to specify the custom converter (don't forget to make the column insertable = false, updateable = false):
/**
* A sample super class that uses RowVersion
*/
#MappedSuperclass
public abstract class SyncPoint {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// type is rowver for SQLServer, blob(8) for postgresql and h2
#Column(name = "current_database_version", insertable = false, updatable = false)
#Convert(converter = RowVersionTypeConverter.class)
private RowVersion currentDatabaseVersion;
#Column(name = "created_date_utc", columnDefinition = "timestamp", nullable = false)
private DateTime createdDate;
...
Also (for this example) here is the Program object we want to find:
#Entity
#Table(name = "program_table")
public class Program {
#Id
private Integer id;
private boolean active;
// type is rowver for SQLServer, blob(8) for postgresql and h2
#Column(name = "rowver", insertable = false, updatable = false)
#Convert(converter = RowVersionTypeConverter.class)
private RowVersion currentDatabaseVersion;
#Column(name = "last_chng_dt")
private DateTime lastUpdatedDate;
...
Now you can use these fields within your JPA criteria queries just like anything else.. here is a snippet that we used inside a spring-data Specifications class:
/**
* Find Programs changed after a synchronization point
*
* #param filter that has the changedAfter sync point
* #return a specification or null
*/
public Specification<Program> changedBeforeOrEqualTo(final ProgramSearchFilter filter) {
return new Specification<Program>() {
#Override
public Predicate toPredicate(Root<Program> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
if (filter != null && filter.changedAfter() != null) {
// load the SyncPoint from the db to get the rowver column populated
SyncPoint fromDb = synchronizationPersistence.reload(filter.changedBeforeOrEqualTo());
if (fromDb != null) {
// real sync point made by database
if (fromDb.getCurrentDatabaseVersion() != null) {
// use binary version
return cb.lessThanOrEqualTo(root.get(Program_.currentDatabaseVersion),
fromDb.getCurrentDatabaseVersion());
} else if (fromDb.getCreatedDate() != null) {
// use timestamp instead of binary version cause db doesn't make one
return cb.lessThanOrEqualTo(root.get(Program_.lastUpdatedDate),
fromDb.getCreatedDate());
}
}
}
return null;
}
};
}
The specification above works with both the binary current database version or a timestamp.. this way I could test my stuff and all the upstream code on a database other than SQLServer.
That's it really: a) type to wrap the byte[] b) JPA converter c) use attribute in query.

JPA query return List<org.datanucleus.store.types.sco.backed.ArrayList> instead List<T>

I'm using JPA on GAE and this query return a List containing 1 element.
This element is a org.datanucleus.store.types.sco.backed.ArrayList (and it's finally containing my results) while I'm expecting a List of Products. What I'm doing wrong?
Thanx in advance!
Query query = entityManager.createQuery
("select p.products from Place p where p.id = :Id" );
query.setParameter("Id",id);
List<Product> resultList = query.getResultList();
//for debugging purpose
assert (resultList.get(0) instanceof Product);
if (resultList.size() > 0)
{
//raise a cast exception here
Product p = resultList.get(0);
}
#Entity
public class Place {
private Collection<Product> products;
#OneToMany(cascade = CascadeType.ALL)
public Collection<Product> getProducts() {
return products;
}
public void setProducts(Collection<Product> products) {
this.products = products;
}
private String id;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Extension(vendorName="datanucleus", key="gae.encoded-pk", value="true")
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
Selecting a multi-valued field from a JPQL query is illegal when the JPQL spec says the select item should be
"single_valued_path_expression | scalar_expression | aggregate_expression |
identification_variable | OBJECT(identification_variable) | constructor_expression"
so answers saying this query is correct are wrong. If you want the "products" for a Place then you retrieve the Place and it has the products.
I am not much familiar with DataNucleus, so haven't executed. But with JPA, the query should work fine. You can try the below code to build query, which returns results according to the specified class.
entityManager.createQuery("SELECT p.products FROM Place p WHERE p.id = :Id", Product.class);
From Documentation :
<T> TypedQuery<T> createQuery(java.lang.String qlString,
java.lang.Class<T> resultClass)
Create an instance of TypedQuery for executing a Java Persistence
query language statement. The select list of the query must contain
only a single item, which must be assignable to the type specified by
the resultClass argument.
Parameters:
qlString - a Java Persistence query string
resultClass - the type of the query result
Edit :
You can try the following code.
Place place = entityManager.createQuery("SELECT p FROM Place p WHERE p.id = :Id", Place.class).getSingleResult();
List<Products> products = place.getProducts();
Also as a side-note, you are using JPA, but #Extension seems to be JDO specific annotation.
That class javadoc tells that it implements java.util.List, so it is a valid return type.
Remember that the specification says that you get a List as a return type, not a java.util.ArrayList, so any class that implements List is as valid as any other.
UPDATE:
Try:
SELECT pr FROM Place pl INNER JOIN pl.products pr WHERE pl.id = :Id

salesforce SOQL : query to fetch all the fields on the entity

I was going through the SOQL documentation , but couldn't find query to fetch all the field data of an entity say , Account , like
select * from Account [ SQL syntax ]
Is there a syntax like the above in SOQL to fetch all the data of account , or the only way is to list all the fields ( though there are lot of fields to be queried )
Create a map like this:
Map<String, Schema.SObjectField> fldObjMap = schema.SObjectType.Account.fields.getMap();
List<Schema.SObjectField> fldObjMapValues = fldObjMap.values();
Then you can iterate through fldObjMapValues to create a SOQL query string:
String theQuery = 'SELECT ';
for(Schema.SObjectField s : fldObjMapValues)
{
String theLabel = s.getDescribe().getLabel(); // Perhaps store this in another map
String theName = s.getDescribe().getName();
String theType = s.getDescribe().getType(); // Perhaps store this in another map
// Continue building your dynamic query string
theQuery += theName + ',';
}
// Trim last comma
theQuery = theQuery.subString(0, theQuery.length() - 1);
// Finalize query string
theQuery += ' FROM Account WHERE ... AND ... LIMIT ...';
// Make your dynamic call
Account[] accounts = Database.query(theQuery);
superfell is correct, there is no way to directly do a SELECT *. However, this little code recipe will work (well, I haven't tested it but I think it looks ok). Understandably Force.com wants a multi-tenant architecture where resources are only provisioned as explicitly needed - not easily by doing SELECT * when usually only a subset of fields are actually needed.
You have to specify the fields, if you want to build something dynamic the describeSObject call returns the metadata about all the fields for an object, so you can build the query from that.
I use the Force.com Explorer and within the schema filter you can click the checkbox next to the TableName and it will select all the fields and insert into your query window - I use this as a shortcut to typeing it all out - just copy and paste from the query window. Hope this helps.
In case anyone was looking for a C# approach, I was able to use reflection and come up with the following:
public IEnumerable<String> GetColumnsFor<T>()
{
return typeof(T).GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
.Where(x => !Attribute.IsDefined(x, typeof(System.Xml.Serialization.XmlIgnoreAttribute))) // Exclude the ignored properties
.Where(x => x.DeclaringType != typeof(sObject)) // & Exclude inherited sObject propert(y/ies)
.Where(x => x.PropertyType.Namespace != typeof(Account).Namespace) // & Exclude properties storing references to other objects
.Select(x => x.Name);
}
It appears to work for the objects I've tested (and matches the columns generated by the API test). From there, it's about creating the query:
/* assume: this.server = new sForceService(); */
public IEnumerable<T> QueryAll<T>(params String[] columns)
where T : sObject
{
String soql = String.Format("SELECT {0} FROM {1}",
String.Join(", ", GetColumnsFor<T>()),
typeof(T).Name
);
this.service.QueryOptionsValue = new QueryOptions
{
batchsize = 250,
batchSizeSpecified = true
};
ICollection<T> results = new HashSet<T>();
try
{
Boolean done = false;
QueryResult queryResult = this.service.queryAll(soql);
while (!finished)
{
sObject[] records = queryResult.records;
foreach (sObject record in records)
{
T entity = entity as T;
if (entity != null)
{
results.Add(entity);
}
}
done &= queryResult.done;
if (!done)
{
queryResult = this.service.queryMode(queryResult.queryLocator);
}
}
}
catch (Exception ex)
{
throw; // your exception handling
}
return results;
}
For me it was the first time with Salesforce today and I came up with this in Java:
/**
* #param o any class that extends {#link SObject}, f.ex. Opportunity.class
* #return a list of all the objects of this type
*/
#SuppressWarnings("unchecked")
public <O extends SObject> List<O> getAll(Class<O> o) throws Exception {
// get the objectName; for example "Opportunity"
String objectName= o.getSimpleName();
// this will give us all the possible fields of this type of object
DescribeSObjectResult describeSObject = connection.describeSObject(objectName);
// making the query
String query = "SELECT ";
for (Field field : describeSObject.getFields()) { // add all the fields in the SELECT
query += field.getName() + ',';
}
// trim last comma
query = query.substring(0, query.length() - 1);
query += " FROM " + objectName;
SObject[] records = connection.query(query).getRecords();
List<O> result = new ArrayList<O>();
for (SObject record : records) {
result.add((O) record);
}
return result;
}
I used following to get complete records-
query_all("Select Id, Name From User_Profile__c")
To get complete fields of record, we have to mention those fields as mentioned here-
https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select.htm
Hope will help you !!!

Getting an element by id in Google App Engine's JDO

I have a problem when trying to find a single element
by id. The entity class is this:
#PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Test{
#PrimaryKey
private String name;
//..and so on.
In this case, the value "name" is acting like an id. I'm using this
query to find the element:
PersistenceManager pm = PMF.get().getPersistenceManager();
Query q = pm.newQuery(Test.class);
q.setFilter("name == " + id);
//String query = "SELECT FROM " + Test.class.getName() + "WHERE name == " + id;
Test test = (Test) q.execute();
return category;
Assuming id is a string value sent as a parameter by other method.
But it doesn't retrieve any data. Also, it throws an exception:
java.lang.ClassCastException:
org.datanucleus.store.appengine.query.StreamingQueryResult cannot be
cast to com.santiagopoli.gapptest.domain.Test
The commented query also doesn't work.
I hope anyone can help me. Is a shame that something that basic is
difficult to achieve. In other sql implementations, it will be easy as
typing "select * from Test where id=". Thanks!
I just fixed it.
Try this...
Test t = pm.getObjectById(Test.class, id);
reference...
http://code.google.com/intl/us/appengine/docs/java/datastore/jdo/creatinggettinganddeletingdata.html

Resources