I am using Hibernate 4 with Spring 4. I have created my own session factory and used Hibernate Transaction Manager. I have a problem while retrieving the data after saving.
I am saving the data using ProcedureCall and in every method I am opening the session and closing the session. What is the problem? If I remove session.close() then it is working fine.
public Map<String, Object> savePurchaseOrderInvoiceDetail(String dataString, String order_no,String event, HttpSession hs) throws SQLException, ParseException {
HibernateTransactionManager htmLocal = (HibernateTransactionManager) hs.getAttribute("HibernateTransactionManager");
Session session = htmLocal.getSessionFactory().openSession();
Transaction tx = getTransaction(session);
ProcedureCall qry = session.createStoredProcedureCall("purchase_order_invoice_api");
qry.registerParameter(0, String.class, ParameterMode.IN).bindValue(event);
qry.registerParameter(1, String.class, ParameterMode.IN).bindValue(dataString);
qry.registerParameter(2, String.class, ParameterMode.OUT);
qry.registerParameter(3, String.class, ParameterMode.OUT);
qry.registerParameter(4, Integer.class, ParameterMode.OUT);
qry.registerParameter(5, String.class, ParameterMode.OUT);
ProcedureOutputs output = qry.getOutputs();
String msg = (String) output.getOutputParameterValue(2);
String voucheNo=(String) output.getOutputParameterValue(3);
int invoiceId=(int) output.getOutputParameterValue(4);
String status=(String) output.getOutputParameterValue(5);
Map<String, Object>map=new HashMap<String, Object>();
map.put("msg", msg);
map.put("voucherNo", voucheNo);
map.put("lastInvoiceId", invoiceId);
map.put("status", status);
tx.commit();
session.close();
return map;
}
public Map<String, Object> getInvoiceDetails(String invoicedId,HttpSession hs) throws Exception{
HibernateTransactionManager htmLocal = (HibernateTransactionManager) hs.getAttribute("HibernateTransactionManager");
Session session = htmLocal.getSessionFactory().openSession();
final Map<String, Object>map=new HashMap<String, Object>();
String company=(String) hs.getAttribute("company");
int invoiceIdInt=Integer.valueOf(invoicedId);
String qry = "select inv.*,get_supplier_name(inv.Company,inv.Identity) AS CUSTOMER_NAME from invoice_tab inv";
Query query = session.createSQLQuery(qry).addEntity(Invoice.class);
query.setCacheable(false);
List<Invoice> invoiceList = query.list();
for (int i = 0; i < invoiceList.size(); i++) {
Invoice invoiceObj=invoiceList.get(i);
//Business logic
}
session.close();
return map;
}
You are trying hard to to use Spring not to mention the fact that you are having a service (or maybe a repository) dependent on the fact that it is a web application. Both things are bad.
Add the #Transactional annotation to the class containing those methods and enable annotation driven transaction management. Instead of passing around the HttpSession simply inject your dependencies, in this case the SessionFactory.
Don't create sessions yourself use the current session, i.e sessionFactory.getCurrentSession() to obtain a transactional session.
#Service
#Transactional
public class YourService {
private final SessionFactory sessionFactory;
public YourService(SessionFactory sf) {
this.sessionFactory=sf;
}
public Map<String, Object> savePurchaseOrderInvoiceDetail(String dataString, String order_no,String event) throws SQLException, ParseException {
Session session = sessionFactory.getCurrentSession();
ProcedureCall qry = session.createStoredProcedureCall("purchase_order_invoice_api");
qry.registerParameter(0, String.class, ParameterMode.IN).bindValue(event);
qry.registerParameter(1, String.class, ParameterMode.IN).bindValue(dataString);
qry.registerParameter(2, String.class, ParameterMode.OUT);
qry.registerParameter(3, String.class, ParameterMode.OUT);
qry.registerParameter(4, Integer.class, ParameterMode.OUT);
qry.registerParameter(5, String.class, ParameterMode.OUT);
ProcedureOutputs output = qry.getOutputs();
String msg = (String) output.getOutputParameterValue(2);
String voucheNo=(String) output.getOutputParameterValue(3);
int invoiceId=(int) output.getOutputParameterValue(4);
String status=(String) output.getOutputParameterValue(5);
Map<String, Object>map=new HashMap<String, Object>();
map.put("msg", msg);
map.put("voucherNo", voucheNo);
map.put("lastInvoiceId", invoiceId);
map.put("status", status);
return map;
}
public Map<String, Object> getInvoiceDetails(int invoicedId, String company) throws Exception{
Session session = sessionFactory.getCurrentSession();
final Map<String, Object>map=new HashMap<String, Object>();
String qry = "select inv.*,get_supplier_name(inv.Company,inv.Identity) AS CUSTOMER_NAME from invoice_tab inv";
Query query = session.createSQLQuery(qry).addEntity(Invoice.class);
query.setCacheable(false);
List<Invoice> invoiceList = query.list();
for (int i = 0; i < invoiceList.size(); i++) {
Invoice invoiceObj=invoiceList.get(i);
//Business logic
}
return map;
}
}
Something like the above.
What I don't really get is why you are even using hibernate as you are creating your own queries and don't use HQL or anything to get entities. The only thing you use hibernate for is mapping and that can be done with plain SQL also, adding hibernate to your project just for the mapping of your sql results is bit overkill in my book.
Related
How to Write the test case for DAO class with JUnit5 and Mockito in spring boot for the code
I am trying to write a test case with JUnit5 and Mockito. How to write the test cases for the below method
DAO class:
public Map<String, Object> addParticipantSubRole(ProductLineParticipantSubRoleDTO subRole, int userId, int buId,int plId) throws RTDataBaseException {
Map<String, Object> returnMap = new HashMap<>();
try {
StoredProcedureQuery query = this.getSession()
.createStoredProcedureCall("PKG_QA_PRODUCT_LINE_ADMIN.PROC_ADD_PARTICIP_SUB_ROLES")
.registerStoredProcedureParameter(1, Integer.class, ParameterMode.IN).setParameter(1, userId)
.registerStoredProcedureParameter(2, Integer.class, ParameterMode.IN).setParameter(2, plId)
.registerStoredProcedureParameter(3, String.class, ParameterMode.IN)
.setParameter(3, subRole.getParticipantSubRoleName())
.registerStoredProcedureParameter(4, String.class, ParameterMode.IN)
.setParameter(4, subRole.getParticipantSubRoleDesc())
.registerStoredProcedureParameter(5, Integer.class, ParameterMode.OUT)
.registerStoredProcedureParameter(6, Integer.class, ParameterMode.OUT)
.registerStoredProcedureParameter(7, String.class, ParameterMode.OUT)
.registerStoredProcedureParameter(8, String.class, ParameterMode.OUT);
int plParticipantSubRoleId = (Integer) query.getOutputParameterValue(5);
int returnId = (Integer) query.getOutputParameterValue(6);
String message = (String) query.getOutputParameterValue(7);
if (returnId == Constants.SUCCESS_INTEGER_VALUE) {
returnMap.put(RESULT, plParticipantSubRoleId);
returnMap.put(RETURN_ID, returnId);
returnMap.put(RETURN_MESSAGE, message);
} else {
returnMap.put(RESULT, subRole);
returnMap.put(RETURN_ID, returnId);
returnMap.put(RETURN_MESSAGE, message);
}
} catch (Exception exception) {
log.error(exception.toString());
throw new RTDataBaseException(userId, "Failed to add PL Sub Role in DB", userId, exception);
}
return returnMap;
}
It makes close to zero sense to do unit testing on the persistance layer. You get a lot of code with close to zero benefit.
Its better to go for an integration test which can look like this ...
#DataJpaTest
class MyDaoTest {
#Autowired private MyDao myDao;
#Test
void testMyDao(){
// Do test setup
myDay.addParticipantSubRole(/* input parameters */)
// Do asserts
}
}
I've set this method to return a response from a Spring Boot rest controller:
public ResponseEntity<Map<String, Object>> get(#PathVariable("id") long id) {
try {
return new ResponseEntity<>(this.ReportDAO.read("dbuser1"), HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
And this is the DAO method:
#Autowired
private JdbcTemplate jdbcTemplate;
public Map<String, Object> read(String testParam) {
List<SqlParameter> parameters = Arrays.asList(new SqlParameter(Types.NVARCHAR));
CallableStatementCreator csc = new CallableStatementCreator() {
#Override
public CallableStatement createCallableStatement(Connection con) throws SQLException {
CallableStatement cs = con.prepareCall("{call test (?)}");
cs.setString(1, testParam);
return cs;
}
};
return jdbcTemplate.call(csc, parameters);
}
I'm successfully having a json object as response but in this format:
#result-set-1: [ {…}, {…} ]
while I'm expecting to have:
[ {…}, {…} ]
Why is the resultset inserted into #result-set-1 key? How can I modify this behaviour?
JdbcTemplate#call returns Map<String, Object> You can alter this behaviour by specifically extracting key from map using key #result-set-1.
This is how i have done it:
sql
CREATE TABLE `sample_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`message` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
Insert statements:
insert into sample_log (message) values('West Country');
insert into sample_log (message) values('Welcome User');
Stored procedure:
CREATE PROCEDURE `fetch_sample_logs`(
in message_query varchar(30)
)
BEGIN
SELECT * FROM new_db.sample_log where message like message_query;
END
Controller
#RequestMapping("/logs")
#RestController
class SampleLogController {
private final JdbcTemplate jdbcTemplate;
#Autowired
SampleLogController(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
#GetMapping("/call")
public Object get() {
final Map<String, Object> call = jdbcTemplate.call(connection -> {
CallableStatement cs = connection.prepareCall("{call fetch_sample_logs (?)}");
cs.setString(1, "%wel%");
return cs;
}, Collections.singletonList(new SqlParameter(Types.VARCHAR)));
return Optional.of(call.getOrDefault("#result-set-1", Collections.emptyList()));
}
}
I am suggesting that you extract all the result-sets and concat them together. You could do as the other answer suggests and just get "#result-set-1" from the Map, but I would suggest at the very least converting the ResultSet to an application-represented object ("Thing" pojo) before returning from the dao method. I think that concatening the result-sets together is probably a more durable solution, unless someone can think of a reason as to why not.
#Autowired
private JdbcTemplate jdbcTemplate;
public List<Thing> read(String testParam) {
List<SqlParameter> parameters = Arrays.asList(new SqlParameter(Types.NVARCHAR));
CallableStatementCreator csc = new CallableStatementCreator() {
#Override
public CallableStatement createCallableStatement(Connection con) throws SQLException {
CallableStatement cs = con.prepareCall("{call test (?)}");
cs.setString(1, testParam);
return cs;
}
};
Map<String, Object> result = jdbcTemplate.call(csc, parameters);
return result.values().stream().map(o -> fromResultSet((ResultSet) o)
.flatMap(List::stream).collect(toList());
}
private List<Thing> fromResultSet(ResultSet resultSet) {
List<Thing> list = new ArrayList<>();
while (resultSet.next()) {
Thing thing = new Thing(resultSet.getString("resultCol1"), resultSet.getString("resultCol2")
list.add(user);
}
}
I modified some code from Resultset To List to actually parse the result set.
I am calling DB2 procedure which takes a input parameter and returns a resultset.
How can i map the O/P to my pojo class.
I have to map the result to nexted pojo classes.
simpleJdbcCall = new SimpleJdbcCall(jdbctemplate)
.withSchemaName("myschema")
.withProcedureName("DB2-PROC")
.declareParameters(
new SqlParameter("1", Types.VARCHAR)
);
Map<String, Object> map = simpleJdbcCall.execute("2020-01-01");
for (Map.Entry<String, Object> entry : map.entrySet()) {
System.out.println("Entry value is " + entry.getValue() );
}
//my o/p
Entry value is [{Col_1=abc, col_2=abc,col_2=xyz, col_2=abc},....];
you can use returningResultSet(parameterName, rowMapper) method to map values to object.
Here some reference code:
SimpleJdbcCall procedureActor = new SimpleJdbcCall(dataSource)
.withSchemaName("myschema")
.withProcedureName("DB2-PROC")
.declareParameters(
new SqlParameter("1", Types.VARCHAR))
.returningResultSet("mapObjRefrence", new RowMapper<Contact>() {
#Override
public Contact mapRow(ResultSet rs, int rowNum) throws SQLException {
YourPojo pojo = new YourPojo();
pojo.setId(rs.getInt("col_1"));
pojo.setName(rs.getString("col_2"));
pojo.setEmail(rs.getString("col_2"));
pojo.setAddress(rs.getString("col_3"));
pojo.setTelephone(rs.getString("col_4"));
return contact;
}
});
Map<String, Object> out = procedureActor.execute("2020-01-01");
List<YourPojo> listPojos = (List<YourPojo>) out.get("mapObjRefrence");
Also you can check for multi table results: How to get multi table results using SimpleJDBCCall in spring?
I am working with DynamoDB with Spring Boot 2.1, and I'm facing an error when I need o user the clause IN during the conditional evaluation. Even with lines that fulfill the requirements, the query result is empty.
How can I return the lines from the table after explicit the result within the IN clause ?
public class DynamoRepository {
private final DynamoDBMapper dynamoDBMapper;
public Optional<List<USER>> query(String id) {
Map<String, String> ean = new HashMap<>();
ean.put("#status", "status");
Map<String, AttributeValue> eav = new HashMap<>();
eav.put(":id", new AttributeValue().withS(documento));
DynamoDBQueryExpression<USER> queryExpression = new DynamoDBQueryExpression<USER>()
.withKeyConditionExpression("id = :id")
.withFilterExpression("#status in (ACTIVE, PENDING)")
.withExpressionAttributeNames(ean)
.withExpressionAttributeValues(eav);
List<USER> query = dynamoDBMapper.query(USER.class, queryExpression);
return query.isEmpty() ? Optional.empty() : Optional.of(query);
}
}
After taking a while, my solution was to define the status' values as Expression Attribute Values like the code below
public class DynamoRepository {
private final DynamoDBMapper dynamoDBMapper;
public Optional<List<USER>> query(String id) {
Map<String, String> ean = new HashMap<>();
ean.put("#status", "status");
Map<String, AttributeValue> eav = new HashMap<>();
eav.put(":id", new AttributeValue().withS(documento));
eav.put(":active", new AttributeValue().withS("ACTIVE"));
eav.put(":pending", new AttributeValue().withS("PENDING"));
DynamoDBQueryExpression<USER> queryExpression = new DynamoDBQueryExpression<USER>()
.withKeyConditionExpression("id = :id")
.withFilterExpression("#status in (:active, :pending)")
.withExpressionAttributeNames(ean)
.withExpressionAttributeValues(eav);
List<USER> query = dynamoDBMapper.query(USER.class, queryExpression);
return query.isEmpty() ? Optional.empty() : Optional.of(query);
}
}
I have a spring batch job that takes in a user query, executes that query to find the selected items, and then I want to insert those items into another database. The problem is that I have to convert elements like dates from the resulting query to insert them again. How can I tell the type of the values returned from the query?
This is what I use to read the items which works properly.
#Bean("querySelectiveItems")
#StepScope
public JdbcCursorItemReader querySelectiveItems(#Qualifier("selectiveSourceDatabase") DataSource dataSource,
#Value("#{jobExecutionContext[" + EtlConfiguration.JOB_PARM_MIGRATION_CONFIG + "]}") MigrationDefinition migrationDefinition
) {
JdbcCursorItemReader reader = new JdbcCursorItemReader<>();
reader.setSql(migrationDefinition.getMigrations().getTable().getQuery());
reader.setDataSource(dataSource);
reader.setRowMapper(new ColumnMapRowMapper());
log.info("Queried for items");
return reader;
}
The following is what I wrote to write to the destination database. The problem is that the values I have to insert are unknown because they are the result of a user query. For example if there is a datatype in my insert statement I must have a TO_DATE around the date value. Is there a way to do this?
#Component
#Lazy
class InsertSelectedItems implements ItemWriter<Map<String, Object>> {
private MigrationDefinition migrationDefinition;
private JdbcTemplate destinationTemplate;
public void setDestinationTemplate(JdbcTemplate destinationTemplate) {
this.destinationTemplate = destinationTemplate;
}
public void setMigrationDefinition(MigrationDefinition migrationDefinition) {
this.migrationDefinition = migrationDefinition;
}
#Override
public void write(List<? extends Map<String, Object>> items) throws Exception {
ArrayList<String> columns = new ArrayList<>();
ArrayList<String> values = new ArrayList<>();
System.out.println(items);
for (Map<String, Object> map : items) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
columns.add(entry.getKey());
values.add(String.valueOf(entry.getValue()));
}
}
String sql = String.format("%s ( %s ) VALUES ( %s ) ",
migrationDefinition.getMigrations().getTable().getInsert(),
String.join(",", columns),
String.join(",", values));
log.info(sql);
destinationTemplate.update(sql);
}
}