ResultSetMetaData using MyBatis - mybatis

How can I get ResultSetMetaData using MyBatis. I can't use INFORMATION_SCHEMA.columns as I have a complex dynamic query joins with multiple tables. For example, I need Number of records(this one I can get based on list size), List of Columns and data type of each column. I browsed a lot and haven't got the right ways to use it.
Please suggest the right way to get ResultSetMetaData either with TypeHandler or some other options with small example ?
I have 2 scenarios.
Scenario 1:
My Query reads a file using Abinitio QueryIt service (It consider files similar to table and can use SQL query to read). Those files are received from various up streams and not fixed set of columns. It can be N number of columns and any data type. Once my query reads the data from file, it will be sent to UI to display in Grid with formatted output based on data type (Integer, Double, String, Date) for user view. To build the Grid model, I need to know number of columns and datatype as well (for formatting). Until I know Number of Columns and Data Type of each column, I can’t build the grid model. Return type of my mapper will be List<Map<String, Object>>.
Scenario 2(for different process): Similar to above method, query reads data from multiple tables instead of reading from file based on criteria selection from UI. Here also number of columns are dynamic for grid based on selection from UI. Hence, I need number of columns and data type of each column. Return type is same as above type.
Thanks in advance.

For complicated queries, you can add custom mappers. I typically create extended mappers in separate directories so they don't get replaced when you generate again. Also, this keeps all custom SQL together.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.company.core.app.db.mapper.custom.SomethingExtendedMapper">
<resultMap id="SomethingMap" type="com.company.core.app.db.Something">
<result column="ID" jdbcType="INTEGER" property="somethingId" />
<result column="LAST_CHANGE_DATE" jdbcType="DATE" property="lastChangeDate"
...
</resultMap>
<select id="getSomething" resultMap="SomethingMap" parameterType="Integer">
select
*
from
something
join something else...
WHERE SOMETHING_ID = #{id}
</select>
</mapper>
Then this would be the interface:
public interface SomethingExtendedMapper {
public List<Something> getSomething(#Param("id") Integer id);
}

You can write a custom TypeHanlder that extends BaseTypeHandler, which would give you access to ResultSetMetaData. I have previously written such a handler to convert a Date to a LocalDate. This is a bit of a hack to your answer because the mapper would not directly return a List<Map<String, Object>>, but that could be a property on the object that you are returning.
#MappedTypes(LocalDate.class)
public class DbLocalDateTypeHandler extends BaseTypeHandler<LocalDate> {
#Override
public void setNonNullParameter(PreparedStatement ps, int i, LocalDate parameter, JdbcType jdbcType) throws SQLException {
if (parameter == null) {
ps.setDate(i, null);
} else {
ps.setDate(i, Date.valueOf(parameter));
}
}
#Override
public LocalDate getNullableResult(ResultSet rs, String columnName) throws SQLException {
Date date = rs.getDate(columnName);
if (date != null) {
return date.toLocalDate();
}
return null;
}
#Override
public LocalDate getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
Date date = rs.getDate(columnIndex);
if (date != null) {
return date.toLocalDate();
}
return null;
}
#Override
public LocalDate getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
Date date = cs.getDate(columnIndex);
if (date != null) {
return date.toLocalDate();
}
return null;
}
}
Then in your resultMap you just need to reference that handler:
<result column="CREATE_DATE" jdbcType="DATE" property="createDate" typeHandler="com.company.core.framework.db.DbLocalDateTypeHandler"/>
Lastly, if you do not need the ResultSetMetaData you could look into creating a custom ObjectFactory.
e.g.
How to return an Optional from MyBatis query
Helpful MyBatis Docs:
http://www.mybatis.org/mybatis-3/configuration.html#typeHandlers
http://www.mybatis.org/mybatis-3/configuration.html#objectFactory

Related

How to find top N elements in Spring Data Jpa?

In Spring Data Jpa to get first 10 rows I can do this findTop10By...(). In my case the number or rows is not defined and comes as a parameter.
Is there something like findTopNBy...(int countOfRowsToGet)?
Here is another way without native query. I added Pageable as a parameter to the method in the interface.
findAllBySomeField(..., Pageable pageable)
I call it like this:
findAllBySomeField(..., PageRequest.of(0, limit)) // get first N rows
findAllBySomeField(..., Pageable.unpaged()) // get all rows
I don't know of a way to do exactly what you want, but if you are open to using #Query in your JPA repository class, then a prepared statement is one alternative:
#Query("SELECT * FROM Entity e ORDER BY e.id LIMIT :limit", nativeQuery=true)
Entity getEntitiesByLimit(#Param("limit") int limit);
Did it by using pagination, as described in the first answer. Just adding a more explicit example.
This example will give you the first 50 records ordered by id.
Repository:
#Repository
public interface MyRepository extends JpaRepository<MyEntity, String> {
Page<MyEntity> findAll(Pageable pageable);
}
Service:
#Service
public class MyDataService {
#Autowired
MyRepository myRepository;
private static final int LIMIT = 50;
public Optional<List<MyEntity>> getAllLimited() {
Page<MyEntity> page = myRepository.findAll(PageRequest.of(0, LIMIT, Sort.by(Sort.Order.asc("id"))));
return Optional.of(page.getContent());
}
}
Found the original idea here:
https://itqna.net/questions/16074/spring-data-jpa-does-not-recognize-sql-limit-command
(which will also link to another SO question btw)

how to pass namedQuery parameters in Apache Camel JPA by header?

I have this camel route:
from("direct:getUser")
.pollEnrich("jpa://User?namedQuery=User.findById&consumeDelete=false");
This is my user Entity:
#Entity
#NamedQueries({
#NamedQuery(name="User.findAll", query="SELECT u FROM User u"),
#NamedQuery(name="User.findById", query="SELECT u FROM User u WHERE u.id = :id")
})
public class User{
#Id
private String id;
}
I have tried this route by setting the header:
from("direct:getUser")
.setHeader("id", simple("myid"))
.pollEnrich("jpa://User?namedQuery=User.findById&consumeDelete=false");
But it is not working
Is there any method to set jpa properties by the headers? The camel documentation quote this in parameters option but i don't found the examples
Options: parameters
This option is Registry based which requires the # notation. This
key/value mapping is used for building the query parameters. It is
expected to be of the generic type java.util.Map where
the keys are the named parameters of a given JPA query and the values
are their corresponding effective values you want to select for. Camel
2.19: it can be used for producer as well. When it's used for producer, Simple expression can be used as a parameter value. It
allows you to retrieve parameter values from the message body header
and etc.
I hope it's not too late to answer. In any case I had a similar issue in my project, the client does a HTTP GET with a parameter id, which is used by the JPA query and the result is finally marshalled back to the HTTP client. I'm running camel in a Spring application.
I finally figured out how to achieve it in a reasonably clean way.
This is the RouteBuilder where the route is defined:
#Override
public void configure() throws Exception {
Class dataClass = SomeClass.class;
JacksonDataFormat format = new JacksonDataFormat();
format.setUnmarshalType(dataClass);
String jpaString = String
.format("jpa://%1$s?resultClass=%1$s&namedQuery=q1" +
"&parameters={\"id\":${headers.id}}", dataClass.getName());
from("jetty://http://localhost:8080/test").toD(jpaString) // note the .toD
.marshal(format)
}
And this is the StringToMapTypeConverter class, otherwise camel cannot convert {"id": X} to a map
public class StringToMapTypeConverter implements TypeConverters {
private static final ObjectMapper mapper = new ObjectMapper();
private static JavaType mapType;
static {
mapType = mapper.getTypeFactory().constructMapType(Map.class,
String.class, Object.class);
}
#Converter
public Map<String, Object> toMap(String map) throws IOException {
return mapper.readValue(map, mapType);
}
}
Remember to add it to the context. In Spring is something like:
<bean id="myStringToMapTypeConverter" class="....StringToMapTypeConverter" />
Refs:
http://camel.apache.org/jpa.html
http://camel.apache.org/message-endpoint.html#MessageEndpoint-DynamicTo
http://camel.apache.org/type-converter.html#TypeConverter-Addtypeconverterclassesatruntime

Stored Procedure OutBound Gateway Return Type for Oracle Clobs?

Attempting to adjust my solution in the following link to handle a CLOB OUT Param. What would the returnType be for handling oracle clobs be? I didn't see reference to it in documentation except for lob handling.
Previous Question - Handling OUT Array Params
<bean id="clobSqlReturnType" class=org.springframework.integration.jdbc.storedproc.?></bean>
<int-jdbc:stored-proc-outbound-gateway
id="outbound-gateway-function-dbms" request-channel="procedureDBMSRequestChannel"
data-source="dataSource"
is-function="true"
stored-procedure-name="get_dbms_output2"
expect-single-result="true">
<int-jdbc:sql-parameter-definition name="c1" type="CLOB" type-name="" direction="OUT" return-type="clobSqlReturnType" />
</int-jdbc:stored-proc-outbound-gateway>
There is no out-of-the-box solution. You should do something yourself. Like this:
public class ClobSqlReturnType implements SqlReturnType {
#Override
public Object getTypeValue(CallableStatement cs, int paramIndex, int sqlType, String typeName) throws SQLException {
Clob clob = cs.getClob(paramIndex);
return clob != null ? clob.getSubString(1, (int) clob.length()) : null;
}
}

MyBatis - ResultMap according to javaType

Hello StackOverflowers,
There is something I don't get about MyBatis resultMap.
The model I'm working on is beeing updated. We decided to create a new graph of objects which reflects our future DB schema (the current one is awful).
To sum up our problem, here is a simple case:
The current Object whith is related to table SITE is org.example.model.SiteModel. We created a new Object called org.example.entity.Site. (The package name is temporary).
The goal is now to use the existing SQL request developed thank to MyBatis and add a new ResultMap linked to the return type of our method.
Here is a an example:
/**
* Get all site defined as template.
*/
#Select("SELECT * FROM SITE WHERE ISTEMPLATE = 'True'")
#ResultMap({"siteResMap" , "siteResultMap"})
#Options(statementType = StatementType.CALLABLE)
<T> List<T> findTemplates();
Then, in an XML configuration file, we defined the following mappings:
...
<resultMap id="siteResMap" type="org.example.entity.Site" />
<resultMap id="siteResultMap" type="org.example.model.SiteModel" />
...
And then we call the method from our DAO:
List<Site> site = siteDao.findTemplates();
List<SiteModel> siteMod = siteDao.findTemplates();
What we are expecting from this is a dynamic interpretation from MyBatis, taking the right ResultMap according to the computed return type.
But both list are shown as List<org.example.entity.Site> from debuger.
It makes me think that the first ResultMap is taken, ignoring the second one.
Am I missing something ? Is there a way to make MyBatis behave in such way ?
Regards
After a lot a research and code exploration, we found out that the String[] of ResultMap is not designed to link java return types to the resultMap.
This is function retrieving the resultmap (from org.apache.ibatis.executor.resultset.DefaultResultSetHandler)
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<Object>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResulSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
It explains why we always got a List of elements of type of the first resultMap.
We created a new Dao to map new object types.

Ormlite and PostgreSQL - Error inserting text array with custom persister

I have been working to setup Ormlite as the primary data access layer between a PostgreSQL database and Java application. Everything has been fairly straightforward, until I started messing with PostgreSQL's array types. In my case, I have two tables that make use of text[] array type. Following the documentation, I created a custom data persister as below:
public class StringArrayPersister extends StringType {
private static final StringArrayPersister singleTon = new StringArrayPersister();
private StringArrayPersister() {
super(SqlType.STRING, new Class<?>[]{String[].class});
}
public static StringArrayPersister getSingleton() {
return singleTon;
}
#Override
public Object javaToSqlArg(FieldType fieldType, Object javaObject) {
String[] array = (String[]) javaObject;
if (array == null) {
return null;
} else {
String join = "";
for (String str : array) {
join += str +",";
}
return "'{" + join.substring(0,join.length() - 1) + "}'";
}
}
#Override
public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) {
String string = (String) sqlArg;
if (string == null) {
return null;
} else {
return string.replaceAll("[{}]","").split(",");
}
}
}
And then in my business object implementation, I set up the persister class on the column likeso:
#DatabaseField(columnName = TAGS_FIELD, persisterClass = StringArrayPersister.class)
private String[] tags;
When ever I try inserting a new record with the Dao.create statement, I get an error message saying tags is of type text[], but got character varying... However, when querying existing records from the database, the business object (and text array) load just fine.
Any ideas?
UPDATE:
PostGresSQL 9.2. The exact error message:
Caused by: org.postgresql.util.PSQLException: ERROR: column "tags" is
of type text[] but expression is of type character varying Hint: You
will need to rewrite or cast the expression.
I've not used ormlite before (I generally use MyBatis), however, I believe the proximal issue is this code:
private StringArrayPersister() {
super(SqlType.STRING, new Class<?>[]{String[].class});
}
SqlType.String is mapped to varchar in SQL in the ormlite code, and so therefore I believe is the proximal cause of the error you're getting. See ormlite SQL Data Types info for more detail on that.
Try changing it to this:
private StringArrayPersister() {
super(SqlType.OTHER, new Class<?>[]{String[].class});
}
There may be other tweaks necessary as well to get it fully up and running, but that should get you passed this particular error with the varchar type mismatch.