Can I use MyBatis to generate Dynamic SQL without executing it? - java

I have some complex queries to build with a number of optional filters, for which MyBatis seems like an ideal candidate for generating dynamic SQL.
However, I still want my query to execute in the same framework as the rest of the application (which is not using MyBatis).
So what I was hoping to do was use MyBatis strictly for generating the SQL, but from there using the rest of my app to actually execute it. Is this possible? If so, how?

Although MyBatis was designed to execute the query after it builds it, you can make use of it's configuration and a little bit of "inside knowledge" to get to what you need.
MyBatis is a very nice framework, unfortunately it lacks on the documentations side so the source code is you friend. If you dig around you should bump into these classes: org.apache.ibatis.mapping.MappedStatement and org.apache.ibatis.mapping.BoundSql which are key players into building the dynamic SQL. Here is a basic usage example:
MySQL table user with this data in it:
name login
----- -----
Andy a
Barry b
Cris c
User class:
package pack.test;
public class User {
private String name;
private String login;
// getters and setters ommited
}
UserService interface:
package pack.test;
public interface UserService {
// using a different sort of parameter to show some dynamic SQL
public User getUser(int loginNumber);
}
UserService.xml mapper file:
<mapper namespace="pack.test.UserService">
<select id="getUser" resultType="pack.test.User" parameterType="int">
<!-- dynamic change of parameter from int index to login string -->
select * from user where login = <choose>
<when test="_parameter == 1">'a'</when>
<when test="_parameter == 2">'b'</when>
<otherwise>'c'</otherwise>
</choose>
</select>
</mapper>
sqlmap-config.file:
<configuration>
<settings>
<setting name="lazyLoadingEnabled" value="false" />
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost/test"/>
<property name="username" value="..."/>
<property name="password" value="..."/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="pack/test/UserService.xml"/>
</mappers>
</configuration>
AppTester to show the result:
package pack.test;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class AppTester {
private static String CONFIGURATION_FILE = "sqlmap-config.xml";
public static void main(String[] args) throws Exception {
Reader reader = null;
SqlSession session = null;
try {
reader = Resources.getResourceAsReader(CONFIGURATION_FILE);
session = new SqlSessionFactoryBuilder().build(reader).openSession();
UserService userService = session.getMapper(UserService.class);
// three users retreived from index
for (int i = 1; i <= 3; i++) {
User user = userService.getUser(i);
System.out.println("Retreived user: " + user.getName() + " " + user.getLogin());
// must mimic the internal statement key for the mapper and method you are calling
MappedStatement ms = session.getConfiguration().getMappedStatement(UserService.class.getName() + ".getUser");
BoundSql boundSql = ms.getBoundSql(i); // parameter for the SQL statement
System.out.println("SQL used: " + boundSql.getSql());
System.out.println();
}
} finally {
if (reader != null) {
reader.close();
}
if (session != null) {
session.close();
}
}
}
}
And the result:
Retreived user: Andy a
SQL used: select * from user where login = 'a'
Retreived user: Barry b
SQL used: select * from user where login = 'b'
Retreived user: Cris c
SQL used: select * from user where login = 'c'

Everyone knows how to use BoundSql.getSql() to get a paramaterized query string from MyBatis, like this:
// get parameterized query
MappedStatement ms = configuration.getMappedStatement("MyMappedStatementId");
BoundSql boundSql = ms.getBoundSql(parameters);
System.out.println("SQL" + boundSql.getSql());
// SELECT species FROM animal WHERE name IN (?, ?) or id = ?
But now you need the other half of the equation, the list of values that correspond to the question marks:
// get parameters
List<ParameterMapping> boundParams = boundSql.getParameterMappings();
String paramString = "";
for(ParameterMapping param : boundParams) {
paramString += boundSql.getAdditionalParameter(param.getProperty()) + ";";
}
System.out.println("params:" + paramString);
// "Spot;Fluffy;42;"
Now you can serialize it to send elsewhere to be run, or you can print it to a log so you can stitch them together and run the query manually.
*code not tested, might be minor type issues or the like

mybatis version is 3.4.5
Util Class
To convert mapper to sql, need mapper interface class,method name,paramters,and sqlSession.
package util;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import org.apache.ibatis.binding.MapperMethod.MethodSignature;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.util.CollectionUtils;
/**
* #author zwxbest - 19-4-25
*/
public class SqlUtil {
public static String showSql(SqlSession sqlSession, Class mapperInterface, String methodName,
Object[] params) {
Configuration configuration = sqlSession.getConfiguration();
MappedStatement ms = configuration.getMappedStatement(
mapperInterface.getName() + "." + methodName);
Method sqlMethod = null;
//find method equals methodName
for (Method method : mapperInterface.getDeclaredMethods()) {
if (method.getName().equals(methodName)) {
sqlMethod = method;
break;
}
}
if (sqlMethod == null) {
throw new RuntimeException("mapper method is not found");
}
MethodSignature method = new MethodSignature(configuration, mapperInterface, sqlMethod);
Object paramObject = method.convertArgsToSqlCommandParam(params);
BoundSql boundSql = ms.getBoundSql(paramObject);
Object parameterObject = boundSql.getParameterObject();
List<ParameterMapping> parameterMappings = boundSql
.getParameterMappings();
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
if (!CollectionUtils.isEmpty(parameterMappings) && parameterObject != null) {
TypeHandlerRegistry typeHandlerRegistry = configuration
.getTypeHandlerRegistry();
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
sql = sql.replaceFirst("\\?",
Matcher.quoteReplacement(getParameterValue(parameterObject)));
} else {
MetaObject metaObject = configuration.newMetaObject(
parameterObject);
for (ParameterMapping parameterMapping : parameterMappings) {
String propertyName = parameterMapping.getProperty();
if (metaObject.hasGetter(propertyName)) {
Object obj = metaObject.getValue(propertyName);
sql = sql
.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
} else if (boundSql.hasAdditionalParameter(propertyName)) {
Object obj = boundSql.getAdditionalParameter(propertyName);
sql = sql
.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
} else {
sql = sql.replaceFirst("\\?", "missing");
}
}
}
}
return sql;
}
/**
* if param's type is `String`,add single quotation<br>
*
* if param's type is `datetime`,convert to string and quote <br>
*/
private static String getParameterValue(Object obj) {
String value = null;
if (obj instanceof String) {
value = "'" + obj.toString() + "'";
} else if (obj instanceof Date) {
DateFormat formatter = DateFormat
.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
value = "'" + formatter.format(new Date()) + "'";
} else if (obj instanceof LocalDateTime) {
value = "\'" + ((LocalDateTime) obj)
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + "\'";
} else {
if (obj != null) {
value = obj.toString();
} else {
value = "";
}
}
return value;
}
}
call example
sqlSession is injected by Spring .
#Autowired
private SqlSession sqlSession;
String sql = SqlUtil
.showSql(sqlSession, PromotionCodeMapper.class, "selectByPromotionCodeForUpdate",
new Object[]{"111"});
log.warn(sql);

public static void main(String[] args) throws Exception {
String script = "<script>select * from table where 1 = 1<if test='id != null'>and id = ${id} </if></script>";
System.out.println(buildSql(script));
}
private static String buildSql(String script) {
LanguageDriver languageDriver = new XMLLanguageDriver();
Configuration configuration = new Configuration();
SqlSource sqlSource = languageDriver.createSqlSource(configuration, script, Object.class);
Map<String, String> parameters = new HashMap<>();
parameters.put("id", "1");
BoundSql boundSql = sqlSource.getBoundSql(parameters);
return boundSql.getSql();
}
use ${id} instead of #{id}
result is: select * from table where 1 = 1 and id = 1

Just to add to Bogdan's correct answer: You need to pass a JavaBean to getBoundSql() with getter's for your interface parameters, if you're interface has a more complex signature.
Let's assume you want to query the user based on the login number and/or the user name. Your interface might look like this:
package pack.test;
public interface UserService {
// using a different sort of parameter to show some dynamic SQL
public User getUser(#Param("number") int loginNumber, #Param("name") String name);
}
I'm leaving out the Mapper code since it's irrelevant for this discussion, but your code in AppTester should become:
[...]
final String name = "Andy";
User user = userService.getUser(i, name);
System.out.println("Retreived user: " + user.getName() + " " + user.getLogin());
// must mimic the internal statement key for the mapper and method you are calling
MappedStatement ms = session.getConfiguration().getMappedStatement(UserService.class.getName() + ".getUser");
BoundSql boundSql = ms.getBoundSql(new Object() {
// provide getters matching the #Param's in the interface declaration
public Object getNumber() {
return i;
}
public Object getName() {
return name;
}
});
System.out.println("SQL used: " + boundSql.getSql());
System.out.println();
[...]

Related

Java Tapestry Autocomplete typeahead include ID on listing

I have a textfield which uses autocomplete mixin on tapestry. The mixin is working fine as it is, but I am having a problem with tagging the values of list of names with duplicate values. Now I am wondering if I can somehow pass the id of the data on autocomplete upon selection.
Here is my code for pupulating the list.
List<String> onProvideCompletionsFromUserName(String partial) {
List<String> matches = new ArrayList<String>();
String partialUpper = partial.toUpperCase();
List<User> users = clientFinder.findUsers();
// int i = 0;
for (User user : users){
String name = NameUtil.toName(user.getFirstName(), user.getFamilyName());
if (name.toUpperCase().contains(partialUpper)) {
matches.add(name );
// if (i++ >= 5) {
// break;
// }
}
}
return matches;
}
Is there a way for me to pass the ID with the list like
(List onProvideCompletionsFromUserName)?
Has anyone encountered this problem as well ? Thanks for your response.
The only way I was able to do it was by extending the Autocomplete mixin with my own version, as the configure method in the mixin is marked protected. Here is my class. Note that I am firing my own event. You will have to give your own values for label, value and uid in the providecompletions handler. The zone parameter is the zone you want to update when the user clicks and item in the completion list.
Mixin:
import java.util.ArrayList;
import java.util.List;
import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.annotations.OnEvent;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.json.JSONLiteral;
import org.apache.tapestry5.json.JSONObject;
import org.got5.tapestry5.jquery.mixins.Autocomplete;
public class UserAutocomplete extends Autocomplete {
public static final String CHANGE_EVENT_NAME = "autocompleteUser";
#Inject
private ComponentResources resources;
#Parameter(defaultPrefix=BindingConstants.LITERAL)
private String zone;
#OnEvent(value = "provideCompletions")
public List<JSONObject> autoComplete(String query) {
List<JSONObject> strings = new ArrayList<JSONObject>();
if(query != null) {
for(User u : service.searchUsers(query.trim())) {
JSONObject so = new JSONObject();
String name = u.getName();
so.put("label", name);
so.put("value", name);
so.put("uid", u.getId());
strings.add(so);
}
}
return strings;
}
protected void configure(JSONObject config) {
config.put("url", resources.createEventLink("autocomplete").toURI());
String url = resources.createEventLink(CHANGE_EVENT_NAME).toURI();
config.put("options", new JSONObject().put("select", new JSONLiteral("function(e, d) {var zone = $('#" + zone + "'); if (!zone) { return; } "
+ "zone.tapestryZone('update', {url: '" + url + "'+'/'+d.item.uid});}")));
}
}
Page Template:
<t:textfield value="query" autocomplete="off" t:mixins="UserAutocomplete" t:zone="resultZone" />
Page Class:
...
#InjectComponent
private Zone resultZone;
#OnEvent(value = UserAutocomplete.CHANGE_EVENT_NAME)
void userChange(Integer id) {
User selectedUser = service.findUser(id);
renderer.addRender(resultZone);
}

how to create custom JDBCrealm?

i'm trying to secure my enterprise web-app!
i have to constraint resources.
Since i have all stored in my db (users and roles), i won't create a fileRealm or store any user's credential in (Glassfish) server. Moreover, i'm using jBCrypt to encrypt users' passwords, so i can't use standard jdbcRealm.
How can i secure my resources?
i'm thinking about custom jdbcRealm, it's the right way? How can i create and use it?
Some existing framework can help me?
Thank you in advance.
I would suggest you to use a framework Apache Shiro. The configuration file is below
[main]
sha256Matcher = org.apache.shiro.authc.credential.HashedCredentialsMatcher
sha256Matcher.hashAlgorithmName = SHA-256
sha256Matcher.hashIterations=1
# base64 encoding
sha256Matcher.storedCredentialsHexEncoded = false
#datasource type
ds = org.apache.shiro.jndi.JndiObjectFactory
#datasourcename
ds.resourceName = cfresource
#datasourcetype
ds.requiredType = javax.sql.DataSource
#configuring jdbc realm
jdbcRealm = com.connectifier.authc.realm.CustomJDBCRealm
jdbcRealm.credentialsMatcher = $sha256Matcher
jdbcRealm.dataSource=$ds
jdbcRealm.userRolesQuery=select name from role where email = ? and isactive=1
jdbcRealm.authenticationQuery=select hash, salt from user where email = ?
jdbcRealm.permissionsLookupEnabled=false
securityManager.realms = $jdbcRealm
#login url
authc.loginUrl = /
#page to redirected to after logout
logout.redirectUrl = /
#page to where to land after login
authc.successUrl = /
#username parameter name in the loginform
authc.usernameParam = username
#password parameter name in the loginform
authc.passwordParam = password
#rememberme parameter name in the loginform
authc.rememberMeParam=rememberme
#cacheManager = org.apache.shiro.cache.ehcache.EhCacheManager
#securityManager.cacheManager = $cacheManager
#jdbcRealm.authenticationCachingEnabled = true
[urls]
# The /login.jsp is not restricted to authenticated users (otherwise no one could log in!), but
# the 'authc' filter must still be specified for it so it can process that url's
# login submissions. It is 'smart' enough to allow those requests through as specified by the
# shiro.loginUrl above.
/* = anon
The CustomJDBCRealm overriding JDBCRealm is below
package com.connectifier.authc.realm;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.shiro.authc.AccountException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.util.JdbcUtils;
import org.apache.shiro.util.SimpleByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* #author kiranchowdhary
*
* Application specific JDBC realm. If required override methods of {#link JdbcRealm} to load users, roles and
* permissions from database.
*
* Do not override configuration in code if it can be done via shiro.ini file.
*/
public class CustomJDBCRealm extends JdbcRealm {
private static final Logger log = LoggerFactory.getLogger(JdbcRealm.class);
public CustomJDBCRealm() {
super();
setSaltStyle(SaltStyle.COLUMN);
}
/**
* overriding the method which is in JdbcRealm. If SaltStyle is COLUMN, then gets String salt value from database
* and forms salt byte array of type {#link ByteSource} with decoded string salt value and sets it to salt value of
* AuthenticationInfo.
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();
// Null username is invalid
if (username == null) {
throw new AccountException("Null usernames are not allowed by this realm.");
}
Connection conn = null;
SimpleAuthenticationInfo info = null;
try {
conn = dataSource.getConnection();
String password = null;
String salt = null;
switch (saltStyle) {
case NO_SALT:
case CRYPT:
case EXTERNAL:
return super.doGetAuthenticationInfo(token);
case COLUMN:
String[] queryResults = getPasswordForUser(conn, username);
password = queryResults[0];
salt = queryResults[1];
break;
}
if (password == null) {
throw new UnknownAccountException("No account found for user [" + username + "]");
}
info = new SimpleAuthenticationInfo(username, password.toCharArray(), getName());
if (salt != null) {
info.setCredentialsSalt(new SimpleByteSource(Base64.decode(salt)));
}
} catch (SQLException e) {
final String message = "There was a SQL error while authenticating user [" + username + "]";
if (log.isErrorEnabled()) {
log.error(message, e);
}
// Rethrow any SQL errors as an authentication exception
throw new AuthenticationException(message, e);
} finally {
JdbcUtils.closeConnection(conn);
}
return info;
}
private String[] getPasswordForUser(Connection conn, String username) throws SQLException {
String[] result;
boolean returningSeparatedSalt = false;
switch (saltStyle) {
case NO_SALT:
case CRYPT:
case EXTERNAL:
result = new String[1];
break;
default:
result = new String[2];
returningSeparatedSalt = true;
}
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(authenticationQuery);
ps.setString(1, username);
// Execute query
rs = ps.executeQuery();
// Loop over results - although we are only expecting one result,
// since usernames should be unique
boolean foundResult = false;
while (rs.next()) {
// Check to ensure only one row is processed
if (foundResult) {
throw new AuthenticationException("More than one user row found for user [" + username
+ "]. Usernames must be unique.");
}
result[0] = rs.getString(1);
if (returningSeparatedSalt) {
result[1] = rs.getString(2);
}
foundResult = true;
}
} finally {
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(ps);
}
return result;
}
}

SAX Parser - How To Handle Bad Data From .XML File

I need to make a program that takes an .xml file that uses the SAX Parser in Java to parse the .xml file, store it in an arrayList and then call methods to display certain objects with the arrayList.
My program needs to be able to handle the user giving the SAX Parser bad data such that if it doesn’t have a certain tag it’s looking for, then it won’t break. I need it to be able to load the data and use a “check” command to check the intergrity of the data. For example, if the customer doest’t have an account associated with it, the program will output which customer doesn’t have an account.
Below, I’ve put the task for the program, the Handler, and the .xml with bad data below.
Task for the program:
check : This command is used to check the integrity of the named entries. In other words, it checks to see that all the entries of a given type are correct. For example, if the command is:
check customer
the program should list all customers (first name and last name) that do not have any accounts. Related commands include:
check account : list any account number without an associated address
check address : list any address without an associated meter
check meter : list any meter id without any meter readings, or whose readings do not match the meter type, e.g., push reading from a polling meter.
.xml File:
<xml version="1.0" encoding="UTF-8">
<!-- Customer with no account -->
<customer lastName ="Anderson" firstName="Thomas">
</customer>
<!-- Account with no address -->
<customer lastName ="Baker" firstName="Susanne">
<account type="residential" accountNumber="999-999-99">
</account>
</customer>
<!-- Address with no meter -->
<customer lastName ="Charles" firstName="Henry">
<account type="residential" accountNumber="888-888-88">
<address type="apartment" unit="308" street="E 6th St." number="56" zipCode="13126"/>
</account>
</customer>
<!-- Meter with no readings -->
<customer lastName ="Davidson" firstName="Mary">
<account type="residential" accountNumber="666-666-66">
<address type="apartment" unit="308" street="W 9th St." number="67" zipCode="13126">
<meter id = "RM-4876-X4" brand="GE" type="poll" location = "West side of building"/>
</address>
</account>
</customer>
<!-- Meter with mismatched readings -->
<customer lastName ="Evans" firstName="Oscar">
<account type="residential" accountNumber="555-555-55">
<address type="house" street="E 10th St." number="78" zipCode="13126">
<meter id = "RM-4874-X4" brand="GE" type="poll" location = "North side">
<meterReading reading="650" date = "1413227815" flag="poll"/>
<meterReading reading="675" date = "1413314215" flag="push"/>
<meterReading reading="622" date = "1413400615" flag="poll"/>
</meter>
</address>
</account>
</customer>
</xml>
Handler File:
package csc241hw07;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
public class MyHandler extends DefaultHandler {
// Variables to hold current values
private ArrayList<Customer> customerList = new ArrayList<Customer>();
private Customer currentCustomer;
private Account currentAccount;
private Address currentAddress;
private Meter currentMeter;
//getter method for employee list
public ArrayList<Customer> getCustList() {
return customerList;
}
#Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
if (qName.equalsIgnoreCase("customer")) {
//Create a customer object
String lastName = attributes.getValue("lastName");
String firstName = attributes.getValue("firstName");
currentCustomer = new Customer(lastName, firstName);
} else if (qName.equalsIgnoreCase("address")) {
// Create an Address object
String street = attributes.getValue("street");
int houseNumber = Integer.parseInt(attributes.getValue("number"));
String zipCode = attributes.getValue("zipCode");
String type = attributes.getValue("type");
String unit = attributes.getValue("unit");
if (type.equalsIgnoreCase("mailing")) {
// this is a mailing address -- assign to current customer
MailingAddress ma = new MailingAddress(street, houseNumber, zipCode, type);
currentCustomer.setMailingAddress(ma);
} else if (type.equalsIgnoreCase("house")) {
// Create a house
currentAddress = new House(street, houseNumber, zipCode, type);
} else if (type.equalsIgnoreCase("commercial")) {
// Create a commercial
currentAddress = new Commercial(street, houseNumber, zipCode, type);
} else if (unit != null) {
// Create an apartment
currentAddress = new Apartment(street, houseNumber, zipCode, type, unit);
} else {
System.out.println("Unknown address type:" + type);
}
if (currentAddress != null) {
// Assign this account to current address
currentAddress.setAccount(currentAccount);
currentAccount.addAddress(currentAddress);
}
} else if (qName.equalsIgnoreCase("meter")) {
// Create a meter object
String type = attributes.getValue("type");
String brand = attributes.getValue("brand");
String id = attributes.getValue("id");
if (type.equalsIgnoreCase("push")) {
currentMeter = new PushMeter(id, brand, type);
} else if (type.equalsIgnoreCase("poll")) {
currentMeter = new PollMeter(id, brand, type);
} else {
System.out.println("Unknown meter type: " + type);
}
if (currentMeter != null) {
// Set location
String location = attributes.getValue("location");
currentMeter.setLocation(currentAddress, location);
currentAddress.addMeter(currentMeter);
}
//System.out.println("METER:");
} else if (qName.equalsIgnoreCase("meterReading")) {
// Create a meter reading
//<meterReading reading="622" date = "1413400615" flag="push"/>
double reading = Double.parseDouble(attributes.getValue("reading"));
//System.out.println("DATE:" );
ZoneOffset z = ZoneOffset.ofHours(5);
long epoch = Long.parseLong(attributes.getValue("date"));
LocalDateTime d = LocalDateTime.ofEpochSecond(epoch,0,z);
//System.out.println("DATE:" + d.toString());
String flag = attributes.getValue("flag");
MeterReading mr = new MeterReading(reading, d, flag, currentMeter);
// Add this to current meter
currentMeter.addReading(mr);
//System.out.println("METERREADING:");
} else if (qName.equalsIgnoreCase("account")) {
// <account type="residential" accountNumber="876-543-21">
String type = attributes.getValue("type");
String acctNum = attributes.getValue("accountNumber");
if (type.equalsIgnoreCase("residential")) {
// residential account
currentAccount = new ResidentialAccount(acctNum, currentCustomer);
} else if (type.equalsIgnoreCase("commercial")) {
currentAccount = new CommercialAccount(acctNum, currentCustomer);
} else {
System.out.println("Unknown account type:" + type);
}
if (currentAccount != null) {
// Add this account to current customer
currentCustomer.addAccount(currentAccount);
}
}
}
#Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (qName.equalsIgnoreCase("customer")) {
customerList.add(currentCustomer);
currentCustomer = null;
} else if (qName.equalsIgnoreCase("meter")) {
currentMeter = null;
} else if (qName.equalsIgnoreCase("account")) {
currentAccount = null;
} else if (qName.equalsIgnoreCase("address")) {
currentAddress = null;
}
}
}
Thank you!
You can add one more list with "bad" customers like:
// Variables to hold current values
private ArrayList<Customer> customerList = new ArrayList<Customer>();
private ArrayList<Customer> badCustomerList = new ArrayList<Customer>();
...
And add some changes sorting those customers out of "good" ones. For instance:
#Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (qName.equalsIgnoreCase("customer")) {
if (isCustomerGood(currentCustomer)) { // Here is checking code
customerList.add(currentCustomer);
} else {
badCustomerList.add(currentCustomer);
}
currentCustomer = null;
} else if (qName.equalsIgnoreCase("meter")) {
currentMeter = null;
} else if (qName.equalsIgnoreCase("account")) {
currentAccount = null;
} else if (qName.equalsIgnoreCase("address")) {
currentAddress = null;
}
}
private static boolean isCustomerGood(Customer customer) {
return customer.getAccount() != null;
}
public boolean check() {
return badCustomerList.isEmpty();
}
public List<Customer> getBadCustomers() {
return badCustomerList;
}
Actually you can implement isCustomerGood differently depending on your needs. Now you just run check method at the end of parsing.
First of all, by bad data I dont think you mean a non-wellformed XML file that causes parsing exception.
If the above assumption is true, then I think you should consider using XPath to query the data file and check for the condition where the target element does not exist...
So why are you not using XPath which would make your code a lot easier to write and maintain?

select all from a JPQL query, with paremeters

I have selection parameters on flat data, only don't know how to either omit a parameter entirely, or make it a complete wildcard. The search might use one or all parameters. How is this done? With ANY or ALL? Or, is there another way?
I would like to use one general query with all the paremeters, and pass in "all" or "any", something along those lines, for some of those parameters.
existing code:
package legacy.database;
import java.sql.Timestamp;
import java.util.List;
import java.util.logging.Logger;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;
public class MyQueries {
private static final Logger log = Logger.getLogger(MyQueries.class.getName());
private final EntityManagerFactory emf = Persistence.createEntityManagerFactory("LegacyDatabasePU");
private final EntityManager em = emf.createEntityManager();
public MyQueries() {
}
public List<Clients> findAll() {
Query q = em.createQuery("select c from Clients c");
List<Clients> clients = q.getResultList();
return clients;
}
public List<Clients> selectWithParameters(Criteria c) {
log.info(c.toString());
String opener = c.getOpener();
String closer1 = c.getCloser1();
String status = c.getStatus();
Query q = em.createQuery(
"SELECT c FROM Clients c "
+ "WHERE c.status like :status "
+ "and c.closer1 like :closer1 "
+ "and c.opener like :opener");
q.setParameter("opener", opener);
q.setParameter("closer1", closer1);
q.setParameter("status", status);
log.info(q.toString());
List<Clients> clients = q.getResultList();
log.fine(clients.toString());
return clients;
}
public Clients findById(int id) {
Clients client = em.find(Clients.class, id);
return client;
}
public void send(int id) {
Clients c = em.find(Clients.class, id);
java.util.Date date = new java.util.Date();
Timestamp t = new Timestamp(date.getTime());
em.getTransaction().begin();
c.setDateUpdated(t.toString());
em.getTransaction().commit();
}
}
In case the parameters are optional, the criteria API provides some more flexibility.
If the selectWithParameters is called often, consider using parameters, since the DB can cache the parametrized query then.
selectWithParameters with optional parameters reads like this:
public List<Clients> selectWithParameters(Criteria criteria) {
log.info(criteria.toString());
String opener = criteria.getOpener();
String closer1 = criteria.getCloser1();
String status = criteria.getStatus();
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Clients> query = criteriaBuilder.createQuery(Clients.class);
Root<Clients> c = query.from(Clients.class);
List<Predicate> wherePredicates = new LinkedList<Predicate>();
if (null != status) {
wherePredicates.add(criteriaBuilder.like(c.get("status"), status));
}
if (null != closer1) {
wherePredicates.add(criteriaBuilder.like(c.get("closer1"), closer1));
}
if (null != opener) {
wherePredicates.add(criteriaBuilder.like(c.get("opener"), opener));
}
query.where(wherePredicates.toArray(new Predicate[0]));
List<Clients> clients = em.createQuery(query).getResultList();
log.fine(clients.toString());
return clients;
}
Thank you, Heiner. This worked, not sure why I had trouble with Heiner's code, but his sample put me in the right direction:
public List<Clients> selectByCriteria(Criteria criteria) {
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Clients> clientCriteriaQuery = criteriaBuilder.createQuery(Clients.class);
Root<Clients> clientRoot = clientCriteriaQuery.from(Clients.class);
clientCriteriaQuery.select(clientRoot);
List<Predicate> predicates = new ArrayList<>();
predicates.add(criteriaBuilder.like(clientRoot.get(Clients_.phone1), "%" + criteria.getPhone1() + "%"));
if (!criteria.getOpener().equalsIgnoreCase("all")) {
predicates.add(criteriaBuilder.like(clientRoot.get(Clients_.opener), "%" + criteria.getOpener() + "%"));
}
if (!criteria.getCloser1().equalsIgnoreCase("all")) {
predicates.add(criteriaBuilder.like(clientRoot.get(Clients_.closer1), "%" + criteria.getCloser1() + "%"));
}
if (!criteria.getStatus().equalsIgnoreCase("all")) {
predicates.add(criteriaBuilder.like(clientRoot.get(Clients_.status), "%" + criteria.getStatus() + "%"));
}
clientCriteriaQuery.where(predicates.toArray(new Predicate[0]));
List<Clients> clients = em.createQuery(clientCriteriaQuery).getResultList();
return clients;
}
There's probably no substantive difference (?) from what Heiner answered. JPA and JPQL are a bit murky. I can't believe it, but I almost prefer SQL! I'll have to adjust.

Can not fix "Unknown table" exception from JOOQ query

I am having trouble getting data from a database I know exists and I know the format of.
In the code snippet below the "if conn != null" is just a test to verify the database name, table name, etc are all correct, and they DO verify.
The last line below is what generates the exception
public static HashMap<Integer, String> getNetworkMapFromRemote(DSLContext dslRemote, Connection conn, Logger logger) {
HashMap<Integer,String> remoteMap = new HashMap<Integer, String>();
// conn is only used for test purposes
if (conn != null) {
// test to be sure database is ok
try
{
ResultSet rs = conn.createStatement().executeQuery("SELECT networkid, name FROM network");
while (rs.next()) {
System.out.println("TEST: nwid " + rs.getString(1) + " name " + rs.getString(2));
}
rs.close();
}
catch ( SQLException se )
{
logger.trace("getNetworksForDevices SqlException: " + se.toString());
}
}
// ----------- JOOQ problem section ------------------------
Network nR = Network.NETWORK.as("network");
// THE FOLLOWING LINE GENERATES THE UNKNOWN TABLE
Result<Record2<Integer, String>> result = dslRemote.select( nR.NETWORKID, nR.NAME ).fetch();
This is the output
TEST: nwid 1 name Network 1
org.jooq.exception.DataAccessException: SQL [select `network`.`NetworkId`, `network`.`Name` from dual]; Unknown table 'network' in field list
at org.jooq.impl.Utils.translate(Utils.java:1288)
at org.jooq.impl.DefaultExecuteContext.sqlException(DefaultExecuteContext.java:495)
at org.jooq.impl.AbstractQuery.execute(AbstractQuery.java:327)
at org.jooq.impl.AbstractResultQuery.fetch(AbstractResultQuery.java:330)
at org.jooq.impl.SelectImpl.fetch(SelectImpl.java:2256)
at com.nvi.kpiserver.remote.KpiCollectorUtil.getNetworkMapFromRemote(KpiCollectorUtil.java:328)
at com.nvi.kpiserver.remote.KpiCollectorUtilTest.testUpdateKpiNetworksForRemoteIntravue(KpiCollectorUtilTest.java:61)
.................
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown table 'network' in field list
.................
For the sake of completness here is part of the JOOQ generated class file for Network
package com.wbcnvi.intravue.generated.tables;
#javax.annotation.Generated(value = { "http://www.jooq.org", "3.3.1" },
comments = "This class is generated by jOOQ")
#java.lang.SuppressWarnings({ "all", "unchecked", "rawtypes" })
public class Network extends org.jooq.impl.TableImpl<com.wbcnvi.intravue.generated.tables.records.NetworkRecord> {
private static final long serialVersionUID = 1729023198;
public static final com.wbcnvi.intravue.generated.tables.Network NETWORK = new com.wbcnvi.intravue.generated.tables.Network();
#Override
public java.lang.Class<com.wbcnvi.intravue.generated.tables.records.NetworkRecord> getRecordType() {
return com.wbcnvi.intravue.generated.tables.records.NetworkRecord.class;
}
public final org.jooq.TableField<com.wbcnvi.intravue.generated.tables.records.NetworkRecord, java.lang.Integer> NWID = createField("NwId", org.jooq.impl.SQLDataType.INTEGER.nullable(false), this, "");
public final org.jooq.TableField<com.wbcnvi.intravue.generated.tables.records.NetworkRecord, java.lang.Integer> NETWORKID = createField("NetworkId", org.jooq.impl.SQLDataType.INTEGER.nullable(false).defaulted(true), this, "");
public final org.jooq.TableField<com.wbcnvi.intravue.generated.tables.records.NetworkRecord, java.lang.String> NAME = createField("Name", org.jooq.impl.SQLDataType.CHAR.length(40).nullable(false).defaulted(true), this, "");
public final org.jooq.TableField<com.wbcnvi.intravue.generated.tables.records.NetworkRecord, java.lang.Integer> USECOUNT = createField("UseCount", org.jooq.impl.SQLDataType.INTEGER.nullable(false).defaulted(true), this, "");
public final org.jooq.TableField<com.wbcnvi.intravue.generated.tables.records.NetworkRecord, java.lang.Integer> NETGROUP = createField("NetGroup", org.jooq.impl.SQLDataType.INTEGER.nullable(false).defaulted(true), this, "");
public final org.jooq.TableField<com.wbcnvi.intravue.generated.tables.records.NetworkRecord, java.lang.String> AGENT = createField("Agent", org.jooq.impl.SQLDataType.CHAR.length(16), this, "");
public Network() {
this("network", null);
}
public Network(java.lang.String alias) {
this(alias, com.wbcnvi.intravue.generated.tables.Network.NETWORK);
}
..........
Based on the "unknown table" exception I thought there was a problem connected to the wrong database or wrong server, but the console output is correct for a JDBC query.
Any thoughts are appreciated, perhaps something else can be the root cause or the DSLContext is not valid (but I would think that would generate a different exception).
The answer ends up being simple, I did not include the .from() method
Result<Record2<Integer, String>> result = dslRemote.select( nR.NETWORKID, nR.NAME )
.from(nR)
.fetch();
That is why the table was unknown, I never put the from method in.

Categories