As a part of my course project we have been asked to annotate our code.
For the following code
import java.lang.annotations.*;
#Target({ElementType.LOCAL_VARIABLE})
#Retention(RetentionPolicy.RUNTIME)
public #interface DBAnnotation {
String variable () default "";
String table () default "";
String column () default "";
boolean isSource () default false;
}
public static void addFileToDB(String fileName, String fileLocation, int offerID){
#DBAnnotation (variable = "fileName", table = "files", column = "FileName", isSource = true)
#DBAnnotation (variable = "fileLocation", table = "files", column = "fileLocation", isSource = true)
String SQLFileSelect = "SELECT FileName FROM files WHERE OfferID = ? AND FileLocation = ?;";
.
.
.
}
I am getting the following error.
Duplicate annotation #File.DBAnnotation. Repeated annotations are allowed only at source level 1.8 or above
But if I change it to ...
public #interface DBAnnotation {
String[] variable () default "";
String table () default "";
String[] column () default "";
boolean[] isSource () default false;
}
.
.
.
#DBAnnotation (
variable = {"fileName","fileLocation"},
table = "files",
column = {"FileName","fileLocation"},
isSource = true)
then it does not give any error.
My concern here is, for the variable fileLocation, would te DBAnnotation be considered as
variable = "fileLocation", table = "files", column = "fileLocation",isSource = true
or will it be considered as
variable = "fileLocation", table = "", column = "fileLocation",isSource =
If you set it like this:
variable = {"fileName","fileLocation"},
table = "files",
column = {"FileName","fileLocation"},
isSource = true
then variable and column is going to be both values, since you defined them as an Array of String.
Important thing here is that what you do with a custom annotation is only up to you (what you do with it at runtime), so :
getAnnotation(DBAnnotation.class).variable(); // will return the String array with both values.
Related
I have my one table like UserTable.
#Entity
public class UserTable{
#PrimaryKey(autoGenerate = true)
private int userId;
private String userName;
private String userEmailId;
// Below code is getter and setter of this class.
}
#Dao
public interface UserDao {
#Query("SELECT * FROM userTable")
public List<UserTable> loadAllUsers();
#Insert
public long insertUserTable(UserTable userTable);
#Insert
public long[] insertUserTables(UserTable... userTables);
#Update
public int updateUserTable(UserTable userTable);
#Delete
public int deleteUserTable(UserTable userTable);
#RawQuery
public abstract List<UserTable> loadAllUserListByGivenIds
(SupportSQLiteQuery query);
public default List<UserTable> loadAllUserListByIds(long[] userIds) {
List<UserTable> list;
ArrayList<Object> argsList = new ArrayList<>();
String selectQuery = "SELECT * FROM UserTable WHERE userId IN (?);";
argsList.add(userIds);
SimpleSQLiteQuery simpleSQLiteQuery = new SimpleSQLiteQuery(selectQuery, argsList.toArray());
list = loadAllUserListByGivenIds(simpleSQLiteQuery);
return list;
}
}
// Now in My MainActivity.class file, I have use following code:
List<UserTable> userList= databaseClient
.getAppDatabase()
.userDao()
.loadAllUserListByIds(new long[]{1L,2L});
My query is running in normal database, but when I was pass array of user ids then, in #RawQuery() method of dao class is not supported for "IN" clause used in where condition "WHERE userId IN (?)".
How, I will use "IN" clause in #RawQuery() of room database.
Much easier to use an #Query it's as simple as:-
#Query("SELECT * FROM UserTable WHERE userId IN (:idList)")
public List<UserTable> getWhatever(long[] idList);
You'd then use getWhatever(new long[]{1L,2L})
If you need it an #rawQuery though you could do it like (used previous answer code for my convenience) :-
private List<TableXEntity> loadAllUserListByIds(int order,long[] idList) {
StringBuilder idListAsCSV = new StringBuilder(); //<<<<<<<<<<
boolean afterFirst = false; //<<<<<<<<<<
//<<<<<<<<<< all of the loop to create the CSV
for (Long l: idList) {
if (afterFirst) {
idListAsCSV.append(",");
}
afterFirst = true;
idListAsCSV.append(String.valueOf(l));
}
StringBuilder sb = new StringBuilder("SELECT * FROM ").append(DBHelper.TableX.NAME);
sb.append(" WHERE " + DBHelper.TableX.COLUMN_ID + " IN(").append(idListAsCSV).append(") "); //<<<<<<<<<<
switch (order) {
case DBHelper.TableX.FIRSTNAME_DESCENDING:
sb.append(DBHelper.TableX.ORDER_BY_FIRSTNAME_DESC);
break;
case DBHelper.TableX.FIRSTNAME_ASCENDING:
sb.append(DBHelper.TableX.ORDER_BY_FIRSTNAME_ASC);
break;
case DBHelper.TableX.LASTNAME_DESCENDING:
sb.append(DBHelper.TableX.ORDER_BY_LASTNAME_DESC);
break;
case DBHelper.TableX.LASTNAME_ASCENDING:
sb.append(DBHelper.TableX.ORDER_BY_LASTNAME_ASC);
break;
default:
break;
}
sb.append(";");
return roomDao.rawq(new SimpleSQLiteQuery(sb.toString(),null));
}
i.e. provide a CSV (although I vaguely recall being able to pass an array)
To use bind arguments (the recommended way as binding arguments protects against SQL injection) then you need a ? for each value and a corresponding array of objects.
So for 3 id's you need IN(?,?,?) and the actual values, the bind arguments, in an Object[]. The following is an example that does this noting that it shows 2 ways of building the Object[] (the bind arguments/values):-
private List<TableXEntity> loadByidList(long[] idlist) {
List<Object> bindargs = new ArrayList<>(); // way 1
Object[] args4Bind = new Object[idlist.length]; // way 2
StringBuilder placeholders = new StringBuilder(); // for the ? placeholders
/* Build the sql before the place holders */
StringBuilder sql = new StringBuilder("SELECT * FROM ")
.append(DBHelper.TableX.NAME)
.append(" WHERE ")
.append(DBHelper.TableX.COLUMN_ID)
.append(" IN (");
boolean afterfirst = false;
int i = 0; /* using for each so have index counter (as opposed to for(int i=0 ....) */
for (long l: idlist) {
bindargs.add(l); // for way 1
args4Bind[i++] = String.valueOf(l); // for way 2
if (afterfirst) {
placeholders.append(",");
}
afterfirst = true;
placeholders.append("?");
}
/* finalise the SQL */
sql.append(placeholders.toString())
.append(");");
//return roomDao.rawq(new SimpleSQLiteQuery(sql.toString(),bindargs.toArray())); // way 1
return roomDao.rawq(new SimpleSQLiteQuery(sql.toString(),args4Bind)); // way 2
}
Please try this, here it has working!
Try this simple trick to pass the arguments for IN operator-
List<Object> argList = new ArrayList<>();
argList.add("3");
argList.add("6");
Then prepare your raw query string:
Note- Match your argument list size with '?' size
String selectQuery = "SELECT * FROM task WHERE id IN (?,?)";
After this pass the raw query string to SimpleSQLiteQuery-
SimpleSQLiteQuery rawQuery = new SimpleSQLiteQuery(selectQuery, args.toArray());
Then fetch the List using DAO:
List<UserTable> taskList1=DatabaseClient
.getInstance(getApplicationContext())
.getAppDatabase()
.userTableDAO()
.getAllList(query);
We can do it in kotlin in the more simpler way.
Let's create two helper methos
object Helper {
fun sqlIn(list: List<Any>, bindArgs: MutableList<Any>): String {
bindArgs.apply { this.addAll(list) }
return "IN (${list.joinToString(",") { "?" }})"
}
fun sqlNotIn(list: List<Any>, bindArgs: MutableList<Any>): String = "NOT ${sqlIn(list, bindArgs)}"
}
Then you can use it in anywhere else
val ids = listOf(1, 2, 3)
val ownerId = 10
val bindArgs = mutableListOf<Any>()
val query = "SELECT * FROM posts WHERE id ${Helper.sqlIn(ids, bindArgs)} AND owner_id = ?"
bindArgs.add(ownerId)
dao.query(
SimpleSQLiteQuery(query, bindArgs.toTypedArray())
)
I have a String SELECT *FROM USERS WHERE ID = '#userid#' AND ROLE = '#role#'
Now i have replace any string between #...# , with a actual value .
Expected output SELECT *FROM USERS WHERE ID = '4' AND ROLE = 'Admin'
This replace will happen from a method , i have written this logic
public String replaceQueryKeyWithValueFromKeyValues(String query, int reportId) {
try {
REPMReportDao repmReportDao = new REPMReportDao();
int Start = 0;
int end;
if (query.contains("#")) {
boolean specialSymbolFound = false;
for (int i = 0; i < query.length(); i++) {
if (query.charAt(i) == '#') {
if (!specialSymbolFound) {
Start = i + 1;
specialSymbolFound = true;
} else {
specialSymbolFound = false;
end = i;
query = query.replace(query.substring(Start - 1, end + 1), repmReportDao.getReportManagerKeyValue(query.substring(Start - 1, end + 1).replaceAll("#", ""), reportId));
}
}
}
return query;
} else {
return query;
}
} catch (Exception e) {
logger.log(Priority.ERROR, e.getMessage());
return e.getMessage();
}
}
It works fine , but in the case if a single '#' symbol exist instead of start and end it will fail.
Like :
SELECT *FROM USERS WHERE emailid = 'xyz#gmail.com' AND ROLE = '#role#'
Here it should replace the only role '#role#' and should left email as it is.
Expected Output => SELECT *FROM USERS WHERE emailid = 'xyz#gmail.com' AND ROLE = 'Admin'
Complete example with mocked data returned by getReportManagerKeyValue:
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class StackOverflow54842971 {
private static Map<String, String> map;
public static void main(String[] args) {
// preparing test data
map = new HashMap<>();
map.put("role", "Admin");
map.put("userid", "666");
// original query string
String query = "SELECT * FROM USERS WHERE ID = '#userid#' AND emailid = 'xyz#gmail.com' AND ROLE = '#role#' ";
// regular expression to match everything between '# and #' with capture group
// omitting single quotes
Pattern p = Pattern.compile("'(#[^#]*#)'");
Matcher m = p.matcher(query);
while (m.find()) {
// every match will be replaced with value from getReportManagerKeyValue
query = query.replace(m.group(1), getReportManagerKeyValue(m.group(1).replaceAll("#", "")));
}
System.out.println(query);
}
// you won't need this function
private static String getReportManagerKeyValue(String key) {
System.out.println("getting key " + key);
if (!map.containsKey(key)) {
return "'null'";
}
return map.get(key);
}
}
It's considered very bad practice to use string substitution to generate database queries, because you leave your code open to SQL Injection attacks. I can't tell from the small code sample you've provided, but the vast majority of large-scale Java projects use the Spring Framework, which allows you to use either JdbcTemplate or (my preference) NamedParameterJdbcTemplate. Both will allow you to substitute variables in a safe manner.
Is there any way to write Java bean to Csv table format using Open Csv ?
What are the other libraries available to achieve this ?
uniVocity-parsers support for conversions to and from java beans is unmatched. Here's a simple example of a class:
public class TestBean {
// if the value parsed in the quantity column is "?" or "-", it will be replaced by null.
#NullString(nulls = {"?", "-"})
// if a value resolves to null, it will be converted to the String "0".
#Parsed(defaultNullRead = "0")
private Integer quantity
#Trim
#LowerCase
#Parsed(index = 4)
private String comments;
// you can also explicitly give the name of a column in the file.
#Parsed(field = "amount")
private BigDecimal value;
#Trim
#LowerCase
// values "no", "n" and "null" will be converted to false; values "yes" and "y" will be converted to true
#BooleanString(falseStrings = {"no", "n", "null"}, trueStrings = {"yes", "y"})
#Parsed
private Boolean pending;
}
Now, to write instances to a file, do this:
Collection<TestBean> beansToWrite = someMethodThatProducesTheObjectYouWant();
File output = new File("/path/to/output.csv");
new CsvRoutines().writeAll(beansToWrite, TestBean.class, output, Charset.forName("UTF-8"));
The library offers many configuration options and ways to achieve what you want. If you find yourself using the same annotations over and over again, just define a meta-annotation. For example, apply a replacement conversion over fields that contain the ` character, instead of declaring this in every single field:
#Parsed
#Replace(expression = "`", replacement = "")
public String fieldA;
#Parsed(field = "BB")
#Replace(expression = "`", replacement = "")
public String fieldB;
#Parsed(index = 4)
#Replace(expression = "`", replacement = "")
public String fieldC;
You can create a meta-annotatin like this:
#Retention(RetentionPolicy.RUNTIME)
#Inherited
#Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
#Replace(expression = "`", replacement = "")
#Parsed
public #interface MyReplacement {
#Copy(to = Parsed.class)
String field() default "";
#Copy(to = Parsed.class, property = "index")
int myIndex() default -1;
And use it in your class like this:
#MyReplacement
public String fieldA;
#MyReplacement(field = "BB")
public String fieldB;
#MyReplacement(myIndex = 4)
public String fieldC;
}
I hope it helps.
Disclaimer: I'm the author of this library, it's open-source and free (Apache V2.0 license)
I know it may be a very silly question, but I am new to DynamoDB.
My doubt is is it possible to update the value of a Range Key in DynamoDB.
Suppose My Table is "TEST"
{
ID : PK/HK
Date : RK
Name : GSI
Add : LSI
}
I want to modify Date Attribute.
Initial Values in Table was:
{
ID = "344"
Date = "5656"
Name = "ABC"
}
Running this code below. I am able to change the Name Attribute which is GSI.
Map<String,AttributeValue> item = new HashMap<String,AttributeValue>();
item.put("ID", new AttributeValue("344"));
item.put("Date", new AttributeValue("5656"));
Map<String,AttributeValueUpdate> item1 = new HashMap<String,AttributeValueUpdate>();
AttributeValueUpdate update = new AttributeValueUpdate().withValue(new AttributeValue("AMIT")).withAction("PUT");
item1.put("Name", update);
UpdateItemRequest updateItemreq = new UpdateItemRequest("Test",item,item1);
UpdateItemResult updateItemres = dynamoDBUSEast.updateItem(updateItemreq);
But When I change this line
item1.put("Name", update);
with
item1.put("Date", update);
I am getting some error as
Exception in thread "main" com.amazonaws.AmazonServiceException: One or more parameter values were invalid: Cannot update attribute Date. This attribute is part of the key (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ValidationException; Request ID: HRRP24Q7C48AMD8ASAI992L6MBVV4KQNSO5AEMVJF66Q9ASUAAJG)
at com.amazonaws.http.AmazonHttpClient.handleErrorResponse(AmazonHttpClient.java:820)
at com.amazonaws.http.AmazonHttpClient.executeHelper(AmazonHttpClient.java:439)
at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:245)
at com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient.invoke(AmazonDynamoDBClient.java:2908)
at com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient.updateItem(AmazonDynamoDBClient.java:1256)
So Is it possible to change the range Key value?
No, like the exception message states, you Cannot update attribute Date. This attribute is part of the key.
You can also see this under the AttributeUpdates documentation:
The names of attributes to be modified, the action to perform on each,
and the new value for each. If you are updating an attribute that is
an index key attribute for any indexes on that table, the attribute
type must match the index key type defined in the AttributesDefinition
of the table description. You can use UpdateItem to update any nonkey
attributes.
The documentation states that you can update any attribute for "an attribute that is an index key attribute for any indexes on that table", which means that when you update an attribute that is projected onto an index, even it is is part of that indexes key, that index will also be updated to reflect the original item.
From the docs of AttributeValueUpdate
You cannot use UpdateItem to update any primary key attributes.
Instead, you will need to delete the item, and then use PutItem to
create a new item with new attributes.
It's a little buried but in docs for UpdateItem it says:
"You can use UpdateItem to update any nonkey attributes."
So, currently the only way to update the primary key of an item is to delete the old item and write a new one.
Here is my implementation of updating id in .net by deleting the item and then recreating it with the new id. I assume java is very similar:
// Based on https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LowLevelDotNetItemsExample.html
public class UpdateId
{
private static string tableName = "MyTableName";
private static AmazonDynamoDBClient client = new AmazonDynamoDBClient();
private static bool isVerbose = false;
public static void ChangeId(string currentId, string newId)
{
try
{
var deletedItem = DeleteItem(currentId);
if (deletedItem.Count == 0)
{
Console.WriteLine($"ERROR: Item to delete not found: {currentId}");
return;
}
deletedItem["Id"] = new AttributeValue
{
S = newId
};
CreateItem(deletedItem);
var updatedItem = RetrieveItem(newId);
if (updatedItem.Count > 0 && updatedItem["Id"].S == newId)
{
Console.WriteLine($"Item id successfully changed from ({currentId}) to ({newId})");
}
else
{
Console.WriteLine($"ERROR: Item id didn't change from ({currentId}) to ({newId})");
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.WriteLine("To continue, press Enter");
Console.ReadLine();
}
}
private static void CreateItem(Dictionary<string, AttributeValue> item)
{
var request = new PutItemRequest
{
TableName = tableName,
Item = item
};
client.PutItem(request);
}
private static Dictionary<string, AttributeValue> RetrieveItem(string id)
{
var request = new GetItemRequest
{
TableName = tableName,
Key = new Dictionary<string, AttributeValue>()
{
{ "Id", new AttributeValue {
S = id
} }
},
ConsistentRead = true
};
var response = client.GetItem(request);
// Check the response.
var attributeList = response.Item; // attribute list in the response.
if (isVerbose)
{
Console.WriteLine("\nPrinting item after retrieving it ............");
PrintItem(attributeList);
}
return attributeList;
}
private static Dictionary<string, AttributeValue> DeleteItem(string id)
{
var request = new DeleteItemRequest
{
TableName = tableName,
Key = new Dictionary<string, AttributeValue>()
{
{ "Id", new AttributeValue {
S = id
} }
},
// Return the entire item as it appeared before the update.
ReturnValues = "ALL_OLD",
// ExpressionAttributeNames = new Dictionary<string, string>()
// {
// {"#IP", "InPublication"}
// },
// ExpressionAttributeValues = new Dictionary<string, AttributeValue>()
// {
// {":inpub",new AttributeValue {
// BOOL = false
// }}
// },
// ConditionExpression = "#IP = :inpub"
};
var response = client.DeleteItem(request);
// Check the response.
var attributeList = response.Attributes; // Attribute list in the response.
// Print item.
if (isVerbose)
{
Console.WriteLine("\nPrinting item that was just deleted ............");
PrintItem(attributeList);
}
return attributeList;
}
private static void PrintItem(Dictionary<string, AttributeValue> attributeList)
{
foreach (KeyValuePair<string, AttributeValue> kvp in attributeList)
{
string attributeName = kvp.Key;
AttributeValue value = kvp.Value;
Console.WriteLine(
attributeName + " " +
(value.S == null ? "" : "S=[" + value.S + "]") +
(value.N == null ? "" : "N=[" + value.N + "]") +
(value.SS == null ? "" : "SS=[" + string.Join(",", value.SS.ToArray()) + "]") +
(value.NS == null ? "" : "NS=[" + string.Join(",", value.NS.ToArray()) + "]")
);
}
Console.WriteLine("************************************************");
}
}
To call it just do this:
UpdateId.ChangeId("OriginalId", "NewId");
I'm calling this function from a jUnit test case with the following information:
// abbr = "US";
// Countries = array of two objects one with iso2 = "us"
public Country getCountryFromAbbr(String abbr) {
abbr = abbr.toLowerCase();
for (int i = 0; i < Countries.size(); i++) {
Country country = Countries.get(i);
String iso2 = country.ISO2.toLowerCase();
String iso3 = country.ISO3.toLowerCase();
if (iso2.equals(abbr) || iso3.equals(abbr)) {
return country;
}
}
return null;
}
When I debug, the second object with ISO2 of us iso2.equals(abbr) is true and the other is false. However, country is not returned and the debugger finishes the loop and returns null.
I'm confused as true || false is true. Am I missing something?
Here's the mock of the countries:
List<Country> Countries = new ArrayList<Country>();
Country country = new Country();
country.CountryId = 1;
country.CountryName = "Great Britian";
country.ISO2 = "GB";
country.ISO3 = "GBR";
Countries.add(country);
Country usa = new Country();
usa.CountryId = Studio.USA_COUNTRY_ID;
usa.CountryName = "United States of America";
usa.ISO2 = "US";
usa.ISO3 = "USA";
Countries.add(usa);
return Countries;
EDIT:
I'm using Eclipse and debugging using my Droid X 2.3.3
Does it work fine with simple if condition?
if (iso2.equals(abbr)) {
return country;
}
if(iso3.equals(abbr)){
return country;
}
This looks like a job for Enum! (whoooooosh!)
public enum Country {
GREAT_BRITAIN("GB", "GBR"),
USA("US", "USA");
private String iso2;
private String iso3;
private Country(String iso2, String iso3){
this.iso2 = iso2;
this.iso3 = iso3;
}
public static Country getCountry(String a){
for (Country c : Country.values()){
if (c.iso2.equalsIgnoreCase(a) || c.iso3.equalsIgnoreCase(a)){
return c;
}
}
return null; // no country found!
}
}
If Country is immutable (you're not going to change any values inside it), then this is a neat way of doing it - and you can add id and name as attributes as well. And expose getters if you need access to those attributes.
Your current method accesses Countries as an instance variable, instead of as an argument to the method, so it's possible there are side-effects there.