Connecting Spring Boot JDBCTemplate to SQL Server (MSSQL) - java

I'm very new to Spring Boot and I'm having trouble trying to set my project up so that it can communicate to SQL Server - more specifically, my JDBCTemplate instance variable is null and for some reason isn't being 'autowired' with the datasource I've specified in my application.properties file. These are the steps I've taken so far:
Using STS I created a new Spring Boot project using the Spring Start Project template.
I selected Gradle to by the 'Type' and ticked JDBC.
I then followed the following tutorial to create an abstract interface (DAO) to SQL Server (http://www.tutorialspoint.com/spring/spring_jdbc_example.htm).
If you scroll down the tutorial page to the MainApp.java bit, the first 4 lines of the main method I did not use - because I don't have a beans.xml file. This is where I presume Spring Boot's #Autowired annotation comes in and creates my beans for me?
I downloaded the SQL Server jar file from Microsoft and added it to my project as an external JAR by right clicking on my project -> Build Path -> Configure Build Path -> Add External JARs..'. Doing this removed an error I had which indicated that the driverClassName I specified in my application.properties file couldn't be found.
I'll start by displaying the contents of my 'application.properties' file:
spring.datasource.url=jdbc:sqlserver://localhost:1433;databaseName=testdb
spring.datasource.username=sa
spring.datasource.password=myPassword
spring.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerConnection
spring.datasource.initialize=true
Below is my 'JDBCTemplate.java' class which contains my CRUD methods:
package demo;
import java.util.List;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
public class BranchJDBCTemplate implements BranchDAO {
private DataSource dataSource;
#Autowired
protected JdbcTemplate jdbcTemplateObject;
#Autowired
#Override
public void setDataSource(DataSource ds) {
this.dataSource = ds;
this.jdbcTemplateObject = new JdbcTemplate(dataSource);
}
#Override
public void create(String name) {
String SQL = "insert into branches (name) values (?)";
jdbcTemplateObject.update(SQL, name);
System.out.println("Created Record Name = " + name);
return;
}
#Override
public Branch getBranch(Integer id) {
String SQL = "select * from branches where id = ?";
Branch student = jdbcTemplateObject.queryForObject(SQL,
new Object[]{id}, new BranchMapper());
return student;
}
#Override
public List<Branch> listBranches() {
String SQL = "select * from branches";
List <Branch> branches = jdbcTemplateObject.query(SQL, new BranchMapper());
return branches;
}
#Override
public void delete(Integer id) {
String SQL = "delete from branches where id = ?";
jdbcTemplateObject.update(SQL, id);
System.out.println("Deleted Record with ID = " + id );
return;
}
#Override
public void update(Integer id, String name) {
String SQL = "update Student set name = ? where id = ?";
jdbcTemplateObject.update(SQL, id);
System.out.println("Updated Record with ID = " + id );
return;
}
}
And finally, here is my 'CustController.java' class which contains the request mapping where I use the JDBCTemplate class to perform a database operation:
package demo;
import java.util.List;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class CustController {
#RequestMapping("/customer")
public Cust customer(#RequestParam(value="name", required=false, defaultValue="World") String name) {
BranchJDBCTemplate branchTemplate = new BranchJDBCTemplate();
List<Branch> branchesList = branchTemplate.listBranches();
for (Branch branch : branchesList) {
System.out.print("ID : " + branch.getId());
}
return new Cust(12, "Test", "Test");
}
}
The issue I'm encountering as mentioned previously is that my jdbcTemplateObject instance ...
protected JdbcTemplate jdbcTemplateObject;
is null and therefore throwing an exception on the following line:
List <Branch> branches = jdbcTemplateObject.query(SQL, new BranchMapper());
It isn't being initialised automatically, can anyone point out what I'm doing wrong?
Many thanks!
Tony

You are right, you need to have beans.xml with datasource configured in it.
In CustController class customer() method, you are using new operator as:
BranchJDBCTemplate branchTemplate = new BranchJDBCTemplate();
and so this branchTemplate instance is not spring manged and so datasource is not autowired resulting in null value of jdbctemplate.
Instead use the annotatioan as:
#Repository("branchDao")
public class BranchJDBCTemplate implements BranchDAO {
...
}
and access branchTemplate in CustController as:
#RestController
public class CustController {
#Autowired
#Qualifier("branchDao")
BranchJDBCTemplate branchTemplate;
...
}

try using the following in your application.properties file
spring.datasource.url=jdbc:sqlserver://localhost:1433;databaseName=testdb;integratedSecurity=false;

Related

Insert embeded document without reading whole document - spring, mongo

I have this factory collection :
#Document(collection = "factory")
public class Factory
{
Private List<Product> products;
}
which embeds the Product as products.
When I have to add a product to an existing factory :
#Autowired
private FactoryRepository factoryRepository;
public void addProduct(Long id, Product product) {
Factory f = factoryRepository.findById(id);
f.addProduct(product);
factoryRepository.save(f);
}
However, the issue is that product is a large object which contains a set of heavy attributes and the factory can have 2000 products.
So, the retrieved factory causes large memory consumption although it is not required in this phase. Is there a way to append a new product object directly into the factory document without reading the whole object?
EDIT:
As for the comments, I tried :
public void addProduct(Long id, Product product) {
Document find = new Document("_id",id);
Document listItem = new Document("products",product);
Document push = new Document("$push", listItem);
collection.updateOne(find,push);
}
This gives error :
org.bson.codecs.configuration.CodecConfigurationException:
Can't find a codec for class product
So I modified to convert it to a string before push :
public void addProduct(Long id, Product product) {
Document find = new Document("_id",id);
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
Document listItem = new Document("products",ow.writeValueAsString(product));
Document push = new Document("$push", listItem);
collection.updateOne(find,push);
}
This pushed object correctly but when reading :
org.springframework.core.convert.ConverterNotFoundException:
No converter found capable of converting from type [java.lang.String] to type [Product]
Still, I got nowhere here. Any ideas on fixing this issue?
You should use MongoTemplate to update product with push to add to existing products. Something like
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import java.util.List;
#SpringBootApplication
public class So62173077Application {
public static void main(String[] args) {
SpringApplication.run(So62173077Application.class, args);
}
#Autowired
private MongoTemplate mongoTemplate;
#Document(collection = "factory")
public class Factory
{
private Long id;
private List<Product> products;
}
public Long createFactory() {
Factory factory = new Factory();
factory.id = 1L;
return mongoTemplate.insert(factory).id;
}
public void addProduct(Long id) {
Query query = new Query();
query.addCriteria(Criteria.where("id").is(id));
Update update = new Update();
Product product = new Product();
product.name = "stackoverflow";
update.push("products", product);
mongoTemplate.updateFirst(query, update, Factory.class);
}
private class Product {
private String name;
}
#Bean
public ApplicationRunner runner() {
return args -> {
//Long id = createFactory();
addProduct(1L);
};
}
}
MongoDB large array may be discouraged (fetching, atomic operations, complex querying).
Depending on your needs but I would suggest you to deal with Product in a brand new "product" collection. Your current issue will be solved as well.
Regards.

Able to connect to postgres through hibernate but not able to get data by performing queries

I have a docker container running a postgres db. The container also create a db, table and some test data. I also have a spring boot hibernate app that is able to connect to the db. I am however not able to perform any queries and get any data.
I have tested to see that I am connected to the correct db by changing the values in my application.properties file to a db name that does not exist - the java app works when I revert back to the correct db name.
I also supplied spring.jpa.show-sql = true in my application.properties file, this prints out the sql command. When I manually run this on the postgres, it returns data.
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
#RestController
public class HelloController {
#Autowired
private AccountService accountService;
#RequestMapping("/hi/{id}")
public String hi(#PathVariable("id") int index) {
return "Hi " + accountService.get(index).getName();
}
#RequestMapping("/all")
public List<String> hey() {
List<String> everyOne = new ArrayList<>();
for (Account account : accountService.list()) {
everyOne.add(account.getName());
}
return everyOne;
}
}
#Service
public class AccountService {
#Autowired
private AccountRepository accountRepository;
public List<Account> list() {
return accountRepository.findAll();
}
public Account get(int index) {
return accountRepository.getOne(index);
}
}
#Repository
public interface AccountRepository extends JpaRepository<Account, Integer> {
}
#Entity
public class Account {
#Id
private int id;
private String name;
public Account() { }
public Account(int id, String name) {
super();
this.id = id;
this.name = name;
}
public int getId() {
return this.id;
}
public String getName() {
return this.name;
}
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
}
spring.datasource.url=jdbc:postgresql://localhost:5432/db
spring.datasource.username= postgres
spring.datasource.password=
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto = update
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation = true
spring.jpa.show-sql = true
Whilst the application is running, if I curl http://localhost:8080/all, I would have expected all the data in the Account table to be returned.
When I curl http://localhost:8080/hi/2, I get an error message saying
'avax.persistence.EntityNotFoundException: Unable to find
com.something.models.Account with id 2'
As I am able to connect to the DB via hibernate and get data when manually running the sql commands that hibernate generates on psql command line, I am sure that I am just missing something simple here. Any help here would be greatly appreciated.
I have figured out the reason why it was not working. My application is working fine as well the docker container that was running the postgres. The issue was that the account table was not in the postgres.
The reason why this was the case was because the tables and insert statements were not running on the db that I had created in postgres but were instead creating tables on the default db (postgres). I realised that when I have broken up by sql scripts in my docker-compose.yml file, for each .sql file, they were running whilst connected to 'postgres' db. By putting in a '\c db' in each of my scripts, this resolved all my issues.

How do I create multiple configurations for the same class with Configuration Admin?

I'm working on an entity-component-system game engine that will utilize the OSGi framework.
I want users/developers to be able to create their own component types in a modular way, similar to the Bethesda Creation Kit.
The way that I had thought about approaching this was to create a class that would represent a component type, then use the Configuration Admin to create configurations, but I'm not sure if my understanding is correct.
I have a class that I want to use as a Component type
#Component(
configurationPid = "Species",
configurationPolicy = ConfigurationPolicy.REQUIRE,
service = Species.class
)
public final class Species {
// ...
}
To test this, I created a command for Apache Gogo to create a Species. My thought was that I should be able to create multiple species with this command.
#Component(
property = {
CommandProcessor.COMMAND_SCOPE + "=species",
CommandProcessor.COMMAND_FUNCTION + "=create"
},
service = CreateSpeciesCommand.class
)
public class CreateSpeciesCommand {
/* L1 */
#Reference(bind = "bindConfigurationAdmin")
private ConfigurationAdmin configurationAdmin;
#Descriptor("creates a species")
public void create(#Descriptor("id of the species") final String speciesId) throws IOException, InvalidSyntaxException {
final String filter = String.format("(%s=%s)", Constants.OBJECTCLASS, Species.class.getSimpleName());
final Configuration[] existingConfigurations = configurationAdmin.listConfigurations(filter);
System.out.println(Arrays.toString(existingConfigurations));
final Configuration speciesConfiguration = configurationAdmin.getConfiguration(Species.class.getSimpleName(), "?");
Dictionary<String, Object> configProperties = new Hashtable<>();
configProperties.put(Constants.SERVICE_PID, "Species");
speciesConfiguration.update(configProperties);
}
}
But all that happens is that it modifies the configuration, instead of creating a new one.
What do I need to do to create multiple configurations for the same class with the Configuration Admin?
2018-06-19 Edit:
Making the changes specified by Peter Kriens' answer:
Add #Designate annotation to Species class
Set #Component#name to something unique
Set #Component#configurationPolicy to ConfigurationPolicy.REQUIRE
Add #ObjectClassDefinition to Species.Config class
Use ConfigurationAdmin#getConfiguration (createConfiguration does not exist, only this and createFactoryConfiguration) with the #Component#name as the pid
results in only one configuration being created, which gets updated with subsequent calls.
OSGi Configuration Admin has 2 different types of configurations:
Singleton – Has a PID
Factory – Has a PID (for the instance of the factory) and a Factory PID for the 'group' of instances.
In OSGi >= 6, you can therefore do this:
#Designate( ocd= Species.Config.class, factory=true )
#Component( name = "species.pid", configurationPolicy=ConfigurationPolicy.REQUIRE )
public class Species {
#ObjectClassDefinition
#interface Config {
String id();
}
#Activate
void activate( Config config) {
System.out.println( config.id() );
}
}
So now the command (extended with list+delete functions):
#Component(
property = {
CommandProcessor.COMMAND_SCOPE + "=species",
CommandProcessor.COMMAND_FUNCTION + "=create",
CommandProcessor.COMMAND_FUNCTION + "=list",
CommandProcessor.COMMAND_FUNCTION + "=delete"
},
service = CreateSpeciesCommand.class
)
public class CreateSpeciesCommand {
#Reference
ConfigurationAdmin configurationAdmin;
public Configuration create(String speciesId) throws Exception {
Configuration c = configurationAdmin.createFactoryConfiguration( "species.pid", "?");
Hashtable<String,Object> d = new Hashtable();
d.put("id", speciesId);
c.update( d );
return c;
}
public Configuration[] list() throws Exception {
return configurationAdmin.
listConfigurations( "(service.factoryPid=species.pid)");
}
public boolean delete(String id) throws Exception {
Configuration[] list = configurationAdmin.
listConfigurations( "(&(service.factoryPid=species.pid)(id="+id+"))");
if ( list == null) {
return false;
}
for ( Configuration c : list ) {
c.delete();
}
return true;
}
}
Some notes:
Written without compiling, so there might be some compile errors
WebConsole is much nicer to create and delete Species
Skipped the Gogo annotations for readability
clearly no defense into 'filter' injection attacks
In order to get a new configuration for each call, I needed to change the following things:
Set the #Component#factory value for the Species class to something unique
Use the ConfigurationAdmin#createFactoryConfiguration method instead of the getConfiguration method
I tried applying the changes specified by Peter Kriens' answer:
Add #Designate annotation to Species class
Set #Component#name to something unique
Set #Component#configurationPolicy to ConfigurationPolicy.REQUIRE
Add #ObjectClassDefinition to Species.Config class
Use ConfigurationAdmin#getConfiguration (createConfiguration does not exist, only this and createFactoryConfiguration) with the #Component#name as the pid; createFactoryConfiguration uses value from #Component#factory
which not only makes new configurations, but also activates the species component at the same time. Not sure why that is, but I'm looking into it.
Updated Code
Species.java
package net.zephyrion.hummingbird.module.species;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
import java.util.Objects;
#Component(
factory = "Species",
configurationPolicy = ConfigurationPolicy.REQUIRE,
service = Species.class
)
public final class Species {
#interface Config {
String id() default "";
}
private Config config;
#Activate
public void configure(final Config config) {
this.config = Objects.requireNonNull(config);
}
private String getId() {
return config.id();
}
}
CreateSpeciesCommand.java
package net.zephyrion.hummingbird.module.species;
import org.apache.felix.service.command.CommandProcessor;
import org.apache.felix.service.command.Descriptor;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import java.io.IOException;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.List;
#Component(
property = {
CommandProcessor.COMMAND_SCOPE + "=species",
CommandProcessor.COMMAND_FUNCTION + "=create"
},
service = CreateSpeciesCommand.class
)
public class CreateSpeciesCommand {
/* L1 */
#Reference(bind = "bindConfigurationAdmin")
private ConfigurationAdmin configurationAdmin;
#Descriptor("creates a species")
public void create(#Descriptor("id of the species") final String speciesId) throws IOException {
try {
final String factoryPid = Species.class.getSimpleName();
final String filter = String.format("(&(id=%s)(service.factoryPid=%s))", speciesId, factoryPid);
final boolean configurationExists = configurationAdmin.listConfigurations(filter) != null;
if (!configurationExists) {
final Configuration speciesConfiguration = configurationAdmin.createFactoryConfiguration(factoryPid, "?");
Dictionary<String, Object> configProperties = new Hashtable<>();
configProperties.put("id", speciesId);
speciesConfiguration.update(configProperties);
}
}
catch (InvalidSyntaxException e) {
e.printStackTrace();
}
}
/* L2 */
private void bindConfigurationAdmin(final ConfigurationAdmin configurationAdmin) {
// TODO Obj.rnn
this.configurationAdmin = configurationAdmin;
}
}

Autowire jdbcTemplate in ObjectMother for a junit 4 integration test

I was wondering if I can somehow autowire jdbcTemplate in the ObjectMother for a junit 4 integration Test.
The purpose of the test is to test that if you attach a document to an employee, you cannot attach afterwards a document with the same name.
For that I made an EmployeeMother that has a method that creates an Employee and a method that inserts it, using jdbcTemplate. If I try to autowire the jdbcTemplate in EmployeeMother it is null(test returns NPE on the first update). It seems I only have access to the applicationContexts from the test itself.
Currently I set it from the test itself, but I would like not to, because I will create more ObjectMothers for different objects and would like not to set the jdbcTemplate for all of them.
Here are the two classes (I deleted the company name from the package and imports):
EmployeeMother:
package com.domain.objMother;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import com.domain.vo.Company;
import com.domain.vo.Contact;
import com.domain.vo.Employee;
import com.domain.vo.Identification;
import com.domain.vo.Role;
public class EmployeeMother {
private final Log log = LogFactory.getLog(getClass());
protected JdbcTemplate jdbcTemplate;
private Employee empJohnDoe;
/**
*
* #return returns an admin user with username djohn
*/
public Employee getEmpJohnDoe() {
empJohnDoe = new Employee();
empJohnDoe.setUserName("djohn");
Role role = new Role();
//as only the id of the role is not nullable I set it as 1 = admin
role.setId(new Long(1));
empJohnDoe.setRole(role);
empJohnDoe.setCompany(new Company());
Identification identity = new Identification();
identity.setFirstName("John");
identity.setLastName("Doe");
identity.setContact(new Contact());
empJohnDoe.setIdentity(identity);
return empJohnDoe;
}
public void setEmpJohnDoe(Employee empJohnDoe) {
this.empJohnDoe = empJohnDoe;
}
/**
* Important! this insert does not cover some details of the Employee:
* It inserts null in the following columns:
* pswd,
* image,
* cnt_id, - should be a list of associated contacts
* salt,
* is_active,
* default_work_hours
* The insert in TAB_IDENTIFICATIONS triggers TRIG_IDNT that inserts stuff in an audit table
* For it to work we need to register a logged user
* That's why we call PAC_SECURITY.PRO_SETCTX('emp_user_name','adminUserName'); (i used an admin)
* I preferred doing this rather than inserting djohn in TAB_EMPLOYEES,
* registering djohn as logged then inserting an identity in TAB_IDENTIFICATIONS
* and then updating djohn with the new identity
* #param emp - Employee to be inserted
*/
public void insert(Employee emp){
jdbcTemplate.update("call PAC_SECURITY.PRO_SETCTX('emp_user_name','adminUserName')");
Long identityId = jdbcTemplate.queryForObject("select max(ti.ID)+1 from tab_identifications ti", Long.class);
emp.getIdentity().setId(identityId);
jdbcTemplate.update(""+
" insert into tab_identifications ("+
" id, first_name, middle_name, last_name, cnp, ci_char, ci_number, birth_date, invalidity,"+
" cas_name, ci_issue_date, ci_issuer, cnt_id"+
" )" +
" values (?,?,?,?,?,?,?,?,?,?,?,?,?)",
new Object[]{emp.getIdentity().getId(), emp.getIdentity().getFirstName(), emp.getIdentity().getMiddleName(),
emp.getIdentity().getLastName(), emp.getIdentity().getCnp(), emp.getIdentity().getIdCardSerial(),
emp.getIdentity().getIdCardNumber(), emp.getIdentity().getBirthDate(),
emp.getIdentity().getInvalidity(), emp.getIdentity().getCAS(), emp.getIdentity().getCiIssueDate(),
emp.getIdentity().getCiIssuer(), emp.getIdentity().getContact().getId()}
);
Long id = jdbcTemplate.queryForObject("select max(te.ID)+1 from tab_employees te", Long.class);
emp.setId(id);
jdbcTemplate.update(""+
" insert into tab_employees (id, user_name, code, pswd, idnt_id, role_id, comp_id, image, "+
" hire_date, cnt_id, salt, is_expired, is_active, default_work_hours "+
" )" +
" values (?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
new Object[]{emp.getId(), emp.getUserName(), emp.getCode(), null, emp.getIdentity().getId(),
emp.getRole().getId(), emp.getCompany().getId(), null, emp.getHireDate(),
null, null, emp.getIsExpired(), null, null
}
);
}
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
HomeEmployeeServiceImplIntegrationTest:
package com.employee.service.impl;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.domain.vo.Document;
import com.domain.vo.Employee;
import com.domain.objMother.EmployeeMother;
import com.employee.service.HomeEmployeeService;
import com.util.annotations.TransactionalDevTest;
import static org.junit.Assert.*;
#RunWith(SpringJUnit4ClassRunner.class)
#TransactionalDevTest
public class HomeEmployeeServiceImplIntegrationTest {
#Autowired
protected JdbcTemplate jdbcTemplate;
#Autowired
HomeEmployeeService homeEmployeeService;
EmployeeMother empMother = new EmployeeMother();
Employee empJohnDoe;
#Before
public void beforeEachTest() throws Exception {
empMother.setJdbcTemplate(jdbcTemplate);
empJohnDoe = empMother.getEmpJohnDoe();
empMother.insert(empJohnDoe);
}
/**
* You should not be able to add a document with the same name
* <code>uploadDocument</code> should not insert the document if it has the same name
*/
#Test
public void shouldNotBeAbleToAddSameDoc(){
Document doc = new Document();
Long id = jdbcTemplate.queryForObject("select max(td.ID)+1 from tab_documents td", Long.class);
doc.setId(id);
doc.setFileName("SameOldDocument");
homeEmployeeService.uploadDocument(empJohnDoe.getIdentity(), doc);
id = jdbcTemplate.queryForObject("select max(td.ID)+1 from tab_documents td", Long.class);
doc.setId(id);
homeEmployeeService.uploadDocument(empJohnDoe.getIdentity(), doc);
Long docNo = jdbcTemplate.queryForObject("select count(id) from tab_documents td where doc_file_name = '" + doc.getFileName() + "'", Long.class);
if(docNo.compareTo(new Long(2)) == 0){
assertEquals("I was able to add a document twice with the same name!", new Long(1), docNo);
}
else{
assertEquals("Something went wrong when adding two documents with the same name! The document should be added once or twice, but the result is different!", new Long(1), docNo);
}
}
TransactionalDevTest is where I define all the applicationContexts used.
The code above works, but I would like to separate EmployeeMother's code and add IdentificationMother and probably DocumentMother each with it's object and insert. I would also like not to set jdbcTemplate for each ObjectMother (things can become ambiguous, some setting jdbcTemplate from the test, some setting it from another ObjectMother).
Thanks in advance.
I was helped by a collegue and this is what I did:
I made an annotation for ObjectMothers:
package com.util.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface ObjectMother {}
I added this annotation to my EmployeeMother:
#ObjectMother
public class EmployeeMother {
and autowired my jdbcTemplate in EmployeeMother (also deleted the getter and setter)
#Autowired
protected JdbcTemplate jdbcTemplate;
I added to an applicationContext:
The xmlns:
xmlns:context="http://www.springframework.org/schema/context"
with schema location
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
and
<context:component-scan base-package="com.domain.objMother">
<context:include-filter type="annotation" expression="com.util.annotations.ObjectMother"/>
</context:component-scan>
This searches all classes annotated with ObjectMother and adds them to the applicationContext (so that you don't have to add them all one by one).
And finnaly I used EmployeeMother autowired in HomeEmployeeServiceImplIntegrationTest and deleted any reference to jdbcTemplate from EmployeeMother in the same class:
#Autowired
EmployeeMother empMother;
Final classes:
EmployeeMother:
package com.domain.objMother;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import com.domain.vo.Company;
import com.domain.vo.Contact;
import com.domain.vo.Employee;
import com.domain.vo.Identification;
import com.domain.vo.Role;
import com.util.annotations.ObjectMother;
#ObjectMother
public class EmployeeMother {
private final Log log = LogFactory.getLog(getClass());
#Autowired
protected JdbcTemplate jdbcTemplate;
private Employee empJohnDoe;
/**
*
* #return returns an admin user with username djohn
*/
public Employee getEmpJohnDoe() {
empJohnDoe = new Employee();
empJohnDoe.setUserName("djohn");
Role role = new Role();
//as only the id of the role is not nullable I set it as 1 = admin
role.setId(new Long(1));
empJohnDoe.setRole(role);
empJohnDoe.setCompany(new Company());
Identification identity = new Identification();
identity.setFirstName("John");
identity.setLastName("Doe");
identity.setContact(new Contact());
empJohnDoe.setIdentity(identity);
return empJohnDoe;
}
public void setEmpJohnDoe(Employee empJohnDoe) {
this.empJohnDoe = empJohnDoe;
}
/**
* Important! this insert does not cover some details of the Employee:
* It inserts null in the following columns:
* pswd,
* image,
* cnt_id, - should be a list of associated contacts
* salt,
* is_active,
* default_work_hours
* The insert in TAB_IDENTIFICATIONS triggers TRIG_IDNT that inserts stuff in an audit table
* For it to work we need to register a logged user
* That's why we call PAC_SECURITY.PRO_SETCTX('emp_user_name','adminUserName'); (i used an admin)
* I preferred doing this rather than inserting djohn in TAB_EMPLOYEES,
* registering djohn as logged then inserting an identity in TAB_IDENTIFICATIONS
* and then updating djohn with the new identity
* #param emp - Employee to be inserted
*/
public void insert(Employee emp){
jdbcTemplate.update("call PAC_SECURITY.PRO_SETCTX('emp_user_name','adminUserName')");
Long identityId = jdbcTemplate.queryForObject("select max(ti.ID)+1 from tab_identifications ti", Long.class);
emp.getIdentity().setId(identityId);
jdbcTemplate.update(""+
" insert into tab_identifications ("+
" id, first_name, middle_name, last_name, cnp, ci_char, ci_number, birth_date, invalidity,"+
" cas_name, ci_issue_date, ci_issuer, cnt_id"+
" )" +
" values (?,?,?,?,?,?,?,?,?,?,?,?,?)",
new Object[]{emp.getIdentity().getId(), emp.getIdentity().getFirstName(), emp.getIdentity().getMiddleName(),
emp.getIdentity().getLastName(), emp.getIdentity().getCnp(), emp.getIdentity().getIdCardSerial(),
emp.getIdentity().getIdCardNumber(), emp.getIdentity().getBirthDate(),
emp.getIdentity().getInvalidity(), emp.getIdentity().getCAS(), emp.getIdentity().getCiIssueDate(),
emp.getIdentity().getCiIssuer(), emp.getIdentity().getContact().getId()}
);
Long id = jdbcTemplate.queryForObject("select max(te.ID)+1 from tab_employees te", Long.class);
emp.setId(id);
jdbcTemplate.update(""+
" insert into tab_employees (id, user_name, code, pswd, idnt_id, role_id, comp_id, image, "+
" hire_date, cnt_id, salt, is_expired, is_active, default_work_hours "+
" )" +
" values (?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
new Object[]{emp.getId(), emp.getUserName(), emp.getCode(), null, emp.getIdentity().getId(),
emp.getRole().getId(), emp.getCompany().getId(), null, emp.getHireDate(),
null, null, emp.getIsExpired(), null, null
}
);
}
}
HomeEmployeeServiceImplIntegrationTest:
package com.employee.service.impl;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.domain.vo.Document;
import com.domain.vo.Employee;
import com.domain.objMother.EmployeeMother;
import com.employee.service.HomeEmployeeService;
import com.util.annotations.TransactionalDevTest;
import static org.junit.Assert.*;
#RunWith(SpringJUnit4ClassRunner.class)
#TransactionalDevTest
public class HomeEmployeeServiceImplIntegrationTest {
#Autowired
protected JdbcTemplate jdbcTemplate;
#Autowired
HomeEmployeeService homeEmployeeService;
#Autowired
EmployeeMother empMother;
Employee empJohnDoe;
#Before
public void beforeEachTest() throws Exception {
empJohnDoe = empMother.getEmpJohnDoe();
empMother.insert(empJohnDoe);
}
/**
* You should not be able to add a document with the same name
* <code>uploadDocument</code> should not insert the document if it has the same name
*/
#Test
public void shouldNotBeAbleToAddSameDoc(){
Document doc = new Document();
Long id = jdbcTemplate.queryForObject("select max(td.ID)+1 from tab_documents td", Long.class);
doc.setId(id);
doc.setFileName("SameOldDocument");
homeEmployeeService.uploadDocument(empJohnDoe.getIdentity(), doc);
id = jdbcTemplate.queryForObject("select max(td.ID)+1 from tab_documents td", Long.class);
doc.setId(id);
homeEmployeeService.uploadDocument(empJohnDoe.getIdentity(), doc);
Long docNo = jdbcTemplate.queryForObject("select count(id) from tab_documents td where doc_file_name = '" + doc.getFileName() + "'", Long.class);
if(docNo.compareTo(new Long(2)) == 0){
assertEquals("I was able to add a document twice with the same name!", new Long(1), docNo);
}
else{
assertEquals("Something went wrong when adding two documents with the same name! The document should be added once or twice, but the result is different!", new Long(1), docNo);
}
}
}

Loading collection of String into model from persistent object with hibernate and spring

When try to place a list of stings taken from an object I've loading in from a database via hibernate I'm getting this exception.
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
The method I've used to load the list is within a transaction. But when I try to place the list in the model I get the above exception. I'm taking from this that hibernate is requiring me to have even this line of code within a transaction also. But given that it's not a database operation why is this so?
#RequestMapping(value="{id}", method=RequestMethod.POST)
public String addComment(#PathVariable String id, Model model, String comment) {
personService.addComment(Long.parseLong(id), comment);
Person person = personService.getPersonById(Long.parseLong(id));
model.addAttribute(person);
List<String> comments = personService.getComments(id);
model.addAttribute(comments);
return "/Review";
}
Service object.
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
public class PersonServiceImpl implements PersonService {
private Workaround personDAO;
public PersonServiceImpl() {
}
#Transactional(propagation=Propagation.REQUIRED, readOnly=false)
public void savePerson(Person person) {
personDAO.savePerson(person);
}
#Transactional(propagation=Propagation.REQUIRED, readOnly=false)
public Person getPersonById(long id) {
return personDAO.getPersonById(id);
}
#Autowired
public void setPersonDAO(Workaround personDAO) {
this.personDAO = personDAO;
}
#Transactional(propagation=Propagation.REQUIRED, readOnly=false)
public List<Person> getAllPeople() {
return personDAO.getAllPeople();
}
#Transactional(propagation=Propagation.REQUIRED, readOnly=false)
public void addComment(Long id, String comment) {
Person person = getPersonById(id);
person.addComment(comment);
savePerson(person);
}
#Transactional(propagation=Propagation.REQUIRED, readOnly=false)
public List<String> getComments(String id) {
return personDAO.getComments(Long.parseLong(id));
}
}
DAO
import java.util.List;
import javax.persistence.ElementCollection;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
#Repository
public class PersonDAO implements Workaround {
private SessionFactory sessionFactory;
#Autowired
public PersonDAO(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
private Session currentSession() {
return sessionFactory.getCurrentSession();
}
public void addPerson(Person person) {
currentSession().save(person);
}
public Person getPersonById(long id) {
return (Person) currentSession().get(Person.class, id);
}
public void savePerson(Person person) {
currentSession().save(person);
}
public List<Person> getAllPeople() {
List<Person> people = currentSession().createQuery("from Person").list();
return people;
}
public List<String> getComments(long id) {
return getPersonById(id).getComments();
}
}
I am relatively new to Hibernate but I'll take a guess at this from my understanding of it.
By default #OneToMany collections are lazily loaded. So when you load your Person object a proxy will be created in place of your actual comments list. This list won't be loaded until you call the getter for that list (ie getComments()) as you are, and even then I don't think the full list is loaded at once, more so one by one (yep multiple db calls) as you iterate through the list or the whole list at one if you call .size().
However, I think the catch here is that you must load the list within the same session that you load the parent (Person) object. When you are loading the Person object you have a reference to the current session and then once you call the getComments() on that Person object the session is closed.
I think to keep the whole process in one session you could manually open and close your session like so in your DAO class.
public List<String> getComments(long id) {
Session session = sessionFactory.openSession();
List<String> comments = getPersonById(id).getComments();
sessionFactory.getCurrentSession().flush();
sessionFactory.getCurrentSession.close();
return comments;
}
Setting the FetchType to EAGER would solve the problem, but from what I have read it is generally not recommended unless you always need the comments loaded with the Person object.
Change a fetch type to FetchType.EAGER.
I think the problem is that you're using
currentSession()
Try to replace it with
private Session currentSession() {
return sessionFactory.openSession();
}
bcause the exception says that there isn't any open session.
You should review the Person class mapping, maybe the Comments field is mapped with the LAZY attribute and so it's not loaded in the DAO. When you call the getComments method Hibernate tries to load the attribute from the database but at that time there is no session, hence the exception. To solve this issue change the mapping attribute to EAGER.

Categories

Resources