How to write a search class to accept any type of parameter? - spring

I'm using spring mvc and I created the CRUD functionality. But I want to create a search function that will allow me to find a user by any parameter (variable) as 'userid' or 'username' or 'lastname' or 'social security number' or whatever.
My userid is an integer type.
How can I do that? What is the SQL query for that?
How can I check if the input is integer or string and then go through the database by the given parameter and search for the user?

If you are using Hibernate for data access you can easily create universal finder using criteria API:
Abstract DAO class:
public abstract class AbstractHibernateDAO<T> {
private static final String PARAM_VALUE_PARAMETER = "paramValue";
private final Class<T> clazz;
#Autowired
private SessionFactory sessionFactory;
public AbstractHibernateDAO(Class<T> clazz) {
this.clazz = clazz;
}
public T findOne(String paramName, Object paramValue) {
Session session = sessionFactory.getCurrentSession();
#SuppressWarnings("unchecked")
T fetchedObject = (T) session.createCriteria(clazz).add(Restrictions.eq(paramName, paramValue)).uniqueResult();
return fetchedObject;
}
// Other CRUD methods.
}
Concrete DAO class for entity:
#Repository
#Transactional
public class ProductHibernateDAO extends AbstractHibernateDAO<Product> {
public ProductHibernateDAO() {
super(Product.class);
}
}
Or if you prefer to use HQL instead of Criteria API you can rewrite search method as:
public T findOne(String paramName, Object paramValue) {
Session session = sessionFactory.getCurrentSession();
StringBuilder queryText = new StringBuilder();
queryText.append("from ");
queryText.append(clazz.getSimpleName());
queryText.append(" where ");
queryText.append(paramName);
queryText.append("=:");
queryText.append(PARAM_VALUE_PARAMETER);
#SuppressWarnings("unchecked")
T fetchedObject = (T) session.createQuery(queryText.toString()).setParameter(PARAM_VALUE_PARAMETER, paramValue).uniqueResult();
return fetchedObject;
}
In this article you can find very good description how to create generic DAO with hibernate (Or if you prefer JPA there are also described how to do this with JPA).
Or if you prefer to use JDBC for data access I recommend you to look at Spring's JdbcTemplate. It simplifies development a lot. Here how you can implement universal finder using JdbcTemplate:
#Repository
#Transactional
public class ProductJDBCDAO implements DAO<Product> {
private static final String TABLE_NAME = "product";
#Autowired
private JdbcTemplate jdbcTemplate;
public Product findOne(String paramName, Object paramValue) {
RowMapper<Product> rowMapper = new RowMapper<Product>(){
public Product mapRow(ResultSet rs, int rowNum) throws SQLException {
long productId = rs.getLong("product_id");
// Other properties
Product product = new Product(...);
return product;
}
};
StringBuilder queryText = new StringBuilder();
queryText.append("select * from ");
queryText.append(TABLE_NAME);
queryText.append(" where ");
queryText.append(paramName);
queryText.append("=?");
Product fetchedObject = jdbcTemplate.queryForObject(queryText.toString(), rowMapper, paramValue);
return fetchedObject;
}
// Other CRUD methods
}
Ass you can see in all examples you don't need explicitly specify parameter type, you just add it as Object parameter.
If you will work with direct JDBC in such case I recommend you to use PreparedStatement and it's setObject(..) method. Query text will be similar to shown in the example with JdbcTemplate.

Related

How to achieve row level authorization in spring-boot?

Assuming I've the following endpoints in spring boot
GET /todo
DELETE /todo/{id}
How can ensure that only entries for the userid are returned and that the user can only update his own todos?
I've a populated Authentication object.
Is there any build in way I can use? Or just make sure to always call findXyzByIdAndUserId where userid is always retrieved from the Principal?
I'm a bit worried about the possibility to forget the check and displaying entries from other users.
My approach to this would be a 3 way implementation: (using jpa & hibernate)
a user request context
a mapped superclass to get your context
a statement inspector to inject your userid
For example:
public final class UserRequestContext {
public static String getUserId() {
// code to retrieve your userid and throw when there is none!
if (userId == null) throw new IllegalStateException("userid null");
return userId;
}
}
#MappedSuperclass
public class UserResolver {
public static final String USER_RESOLVER = "USER_RESOLVER";
#Access(AccessType.PROPERTY)
public String getUserId() {
return UserRequestContext.getUserId();
}
}
#Component
public class UserInspector implements StatementInspector {
#Override
public String inspect(String statement) {
if (statement.contains(UserResolver.USER_RESOLVER)) {
statement = statement.replace(UserResolver.USER_RESOLVER, "userId = '" + UserRequestContext.getUserId() + "'" );
}
return sql;
}
#Bean
public HibernatePropertyCustomizer hibernatePropertyCustomizer() {
return hibernateProperies -> hibernateProperties.put("hibernate.session_factory.statement_inspector",
UserInspector.class.getName());
}
}
So your Entity looks like this:
#Entity
...
#Where(clause = UserResolver.USER_RESOLVER)
public class Todo extends UserResolver {
...
}

SPRING JPA Lazy loading data to use in other class

I'm using Spring Boot for a project, I'm stuck with lazy loading.
What I want to do is load data in my controller, then send to presentable object, that will extract needed information and the JSON serializer do the bad work to create my custom HTTP response.
the problem occurs when the UserPresentation class calls the folder getter, the error is the well known: could not initialize proxy - no Session.
Of course the default fetch is LAZY for the folder and I want this, but I don't know how to prepare the object to be usable in the Presentation.
I copy-pasted only Folder set to be clear and short, but I've more collection inside User class, all of them give me the same problem.
I know that I could call getter in controller just to initialize Collections, but I find this like an hardcoding, in fact if I want add something to presentable I need to do in controller too.
I've tried too with #Transactional but not works.
Here are my class:
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "USER_ID")
private Integer id;
#Column(unique = true)
private String email;
private String password;
#Enumerated(EnumType.STRING)
private Authority userAuthority;
#OneToMany(mappedBy = "owner", cascade = CascadeType.ALL)
private Set<Folder> ownFolders = new HashSet<>();
... getter setter
}
#RestController
public class UserController {
#GetMapping(value = "/api/user", produces = APPLICATION_JSON_VALUE)
public CustomResponseEntity userInfo() {
User currentUser = loginService.getCurrentUser();
UserPresentation userPresentation = new UserPresentation(currentUser);
return ResponseManager.respondData(userPresentation);
}
}
public class UserPresentation implements Presentable {
private User user;
public UserPresentation(User user) {
this.user = user;
}
public Integer getId() {
return user.getId();
}
public String getEmail() {
return user.getUsername();
}
public String getAuthority() {
return user.getUserAuthority().name();
}
public boolean isEnabled() {
return user.isEnabled();
}
public Integer getOwnFolders() {
Set<Folder> folderList = user.getOwnFolders();
if (folderList == null)
return 0;
return folderList.size();
}
}
Last two just to be clear
public class ResponseManager {
// DATA
public static ResponseEntity respondData(Presentable presentable, String token) {
CustomResponse response = new DataResponse<>(presentable);
return new ResponseEntity<>(response, HttpStatus.OK);
}
}
public class DataResponse<T extends Presentable> extends CustomResponse {
private T data;
public T getData() {
return data;
}
private void setData(T data) {
this.data = data;
}
public DataResponse(T data) {
this.setData(data);
}
#Override
public String getType() {
return DATA;
}
}
I suppose you load the current user form the database with:
User currentUser = loginService.getCurrentUser();
and the getCurrentUser() method is transactional. You can either:
Use JPQL like this:
"select u from User u join fetch u.ownFolders where ... " to load the user's info (this way ownFolders relation is eagerly fetched)
or
Simply call user.getOwnFolders() inside getCurrentUser() to trigger
the fetch.
I found a way, even is a little bit dirty it allows me to do what I want without big change at the code.
Practically the problem occurs during the JSON serialization, that run outside of my control (somewhere inside Spring classes just before send HTTP response), so I manually serialized every Presentable object inside a #Transactional block just after its creation.
These are the changed classes:
public class UserPresentation implements Presentable {
private User user;
public UserPresentation(User user) {
this.user = user;
this.initialize() //ADDED (called here and in every other class that implements Presentable)
}
...getter and setter (which I want as JSON fields)
}
#RestController
public class UserController {
#Transactional //ADDED
#GetMapping(value = "/api/user", produces = APPLICATION_JSON_VALUE)
public CustomResponseEntity userInfo() {
User currentUser = loginService.getCurrentUser();
UserPresentation userPresentation = new UserPresentation(currentUser);
return ResponseManager.respondData(userPresentation);
}
}
Before this fix, the interface was used only to use Polymorfism inside ResponseManager, so was empty
public interface Presentable {
default void initialize() {
try {
new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
throw new RuntimeJsonMappingException(e.getMessage());
}
}
}
I would suggest you use https://github.com/FasterXML/jackson-datatype-hibernate
The module supports datatypes of Hibernate versions 3.x , 4.x and 5.x; as well as some of the associated behavior such as lazy-loading and detection of transiency (#Transient annotation).
It knows how to handle Lazy loading after the session is closed , it will skip the json conversion for objects marked as Lazy fetch when outside session
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate5</artifactId>
<version>2.9.8</version>
</dependency>
ObjectMapper mapper = new ObjectMapper();
// for Hibernate 4.x:
mapper.registerModule(new Hibernate4Module());
// or, for Hibernate 5.x
mapper.registerModule(new Hibernate5Module());
// or, for Hibernate 3.6
mapper.registerModule(new Hibernate3Module());
#Configuration
public class WebMvcConfig implements WebMvcConfigurer {
/*
* Here we register the Hibernate4Module into an ObjectMapper, then set this * custom-configured ObjectMapper to the MessageConverter and return it to be * added to the HttpMessageConverters of our application
*/
public MappingJackson2HttpMessageConverter jacksonMessageConverter() {
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper hibernateAwareObjectMapper = new ObjectMapper();
hibernateAwareObjectMapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
hibernateAwareObjectMapper.enable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
// Registering Hibernate5Module to support lazy objects
hibernateAwareObjectMapper.registerModule(new Hibernate5Module());
messageConverter.setObjectMapper(hibernateAwareObjectMapper);
return messageConverter;
}
}
XML config
<mvc:annotation-driven>
<mvc:message-converters>
<!-- Use the HibernateAware mapper instead of the default -->
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="path.to.your.HibernateAwareObjectMapper" />
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>

Spring MongoRepository custom query

Hi I am new to using Spring with MongoRepository and I'm working on creating a custom query for MongoDB using Spring's MongoRepository.
What I would like to do is return a custom query for another variable in my model instead of the Object id.
for my model I have:
#Document(collection = "useraccount")
public class UserAccounts {
#Id
private String id;
private String accountNumber;
private String firstName;
private String lastName;
// getters and setters
}
inside of my repository I just extend the generic MongoRepository:
#Repository
public interface UserAccountsRepository extends MongoRepository<UserAccounts, String> {
}
I am trying to create a custom query that returns the accountNumber inside of my UserAccountsService:
#Service
public class UserAccountsService {
private final UserAccountsRepository userAccountsRepository;
public UserAccountsService(UserAccountsRepository userAccountsRepository) {
this.userAccountsRepository = userAccountsRepository;
}
// generic find by Object id
public UserAccounts findOne(String id) {
Optional<UserAccounts> userAccountsOptional =
userAccountsRepository.findById(id);
if(!userAccountsOptional.isPresent()) {
throw new RuntimeException("User Account Not Found");
}
return userAccountsOptional.get();
}
// would like to implement custom query to return UserAccount if
// found by accountNumber variable
public UserAccounts findOneByUserAccountNumber(String accountNumber) {
return dormantAccountsRepository.findOne(*need query here*);;
}
}
How would I go about creating a custom query to find a User Account by the accountNumber instead of the object id?
Any help would be great thanks!

Query and Database performance when used with JpaRepository findAll() vs native query using JpaRepository

I am developing a spring boot project where i am having two functions for JPA on which i need to figure out which function will perform better and put less pressure on database query performance and utilise Hibernate caching. Please guide on which query to use.
My Repository interface:
#Repository
public interface CustomersRepository
extends JpaRepository<CustomersEntity, Long> {
#Query(nativeQuery = true, value = "SELECT * FROM customers WHERE c_mobile = ?1")
CustomersEntity findcustomerByMobile(String mobileNo);
#Override
List<CustomersEntity> findAll();
}
My Service class:
#Scope("request")
#Service
public class CustomerServiceImpl implements ICustomerService {
#Autowired
private CustomersRepository customersRepository;
#Override
public boolean findCustomerByMobile1(long mobileNo) {
CustomersEntity customersEntity = customersRepository.findcustomerByMobile(mobileNo);
if (customersEntity != null)
return true;
else
return false;
}
#Override
public boolean findCustomerByMobile2(long mobileNo) {
List<CustomersEntity> entityList = customersRepository.findAll();
for (CustomersEntity entity : entityList) {
if (entity.getcMobile() == mobileNo) {
return true;
}
}
return false;
}
}
There is no need to download all records from the database to your app and then filtering them. With thousands of records it will slow down.
Instead you should create an index on c_mobile field then use just like this simple method:
public interface CustomerRepo extends JpaRepository<CustomersEntity, Long> {
CustomersEntity findByMobileNo(String mobileNo);
}
It will work in a flash (with index).
More info about building query methods you can find here.

About spring Rowmapper , mapRow

I have a some questions about Spring rowmapper. I'm going to receive data from my DB using rowmapper.But my command object 'Table' only have List variable.
is Rowmapper automatically map each record to List ? is it posibble?
i know spring bind tag is automatically bind value to list.
right this.
Table.java
public class Table implements Serializable{
private List<String> tableNum = new ArrayList<String>();
// setter and getter
}
Dao
private class TableRowMapper implements RowMapper {
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
Table table = new Table();
table.setTableNum(rs.getString("TABLE_LOCATION"));
return table;
}
}
The RowMapper is used to map a single row to a single domain object, not a bunch of rows results to a single domain object. Also the RowMapper isn't a Dao type object. It is to be used with some query method, like JdbcTemplate.query(sql,args,rowMapper)
But in your case, you don't want a RowMapper. You should instead just use a JdbcTemplate.queryForList. See the JdbcTemplate API for more query method. A simple example would be something like:
public class YourDaoImpl extends JdbcTemplate implements YourDao {
private static final String SQL =
"select SOME_FIELD from SOME_TABLE where SOMETHING = ?";
#Override
public List<String> getSomeFieldBySomething(String something) {
return (List<String>)queryForList( SQL,
new Object[] { something },
String.class);
}
}
You use the dao for your services.
UPDATE
Because of your help, I can get a one column from my DB. but I got a problems. my db table is made of multiple columns. and i must receive all of them. and.. how can i do it? plz help me~!!!
You posted question in no points that out. In this case you need to make a List<DomainObject>. Not a List<String>. List<String> only allows for one value. If you have a List<DomainObject>, then the class DomainObject can have all your fields. Then that's when you use the RowMapper. And you can still use queryForList that uses the RowMapper variant
public class Table {
private String field1;
private String field2;
private String field3;
// getters and setters
}
public class YourDaoImpl extends JdbcTemplate implements YourDao {
private static final String SQL =
"select * from SOME_TABLE where SOMETHING = ?";
#Override
public List<Table> getTableBySomething(String something) {
return (List<Table>)queryForList( SQL,
new Object[] { something },
new RowMapper<Table>(){
#Override
public Table mapRow(ResultSet rs, int rowNumber) {
Table table = new Table();
table.setField1(rs.getString("feild1"));
// set the others
return table;
}
});
}
}
An aside, if I were you, I would forget the jdbc and go for an ORM framework like JPA. If you want entire domain objects, this is the way to go.

Resources