Persist object directly to db using mybatis - java

I am new to mybatis. Previously I used Hibernate ORM framework.Now I want to use mybatis for development.
Below is my mapper class where I can write the actual query for application.
public interface UserMapper {
#Insert("INSERT into user(id,name,address) VALUES(#{uid}, #{uname}, #{uaddress})")
void insertUser(User user);
}
I am getting the User information in request from front end application.
Below is my Controller::
#RequestMapping(value = "/userdetails", method = RequestMethod.POST, headers = "Accept=application/json")
public #ResponseBody ResponseCodeModel userInfo(#RequestBody User user)
{
session.save(user);//In Hibernate
}
If I used Hibernate I can directly persist the user object in db using session.save(userObject);
But in Mybatis ,I need to map all the user parameter in mapper query.
If my table having 50 coloumn then I need to mention all the parameter in query like below::
public interface UserMapper {
#Insert("INSERT into user(id,name,address,....50 Coloumn name) VALUES(#{uid}, #{uname}, #{uaddress} ,....50 model attribute name)")
void insertUser(User user);
}
Is there any easier way to persist the Model Object in DB using myBatis.

Mybatis is SQL Mapper, not ORM. then you indeed have to map.
If you insert into all columns in the table then you may omit the columns name and specify values in right order, this is common SQL.

Related

Insert new entity: Spring Data JPA vs. Hibernate's EntityManager

Please, look at the two code examples bellow which I'm going to use in my Spring Boot project. They both do merely the same thing - add a new object into users table, represented by User entity with username defined as #Id and a unique constraint imposed on email column (there are some other columns as well, but they are not shown here for brevity). Note: I can't simply use save() method from CrudRepository, because it merges existing record with new object if they both have the same username value. Instead, I need to insert a new object with appropriate exception thrown for duplicate data persistence.
My question is about which option should be given a favor. With EntityManager, I don't need to construct SQL statement. Apart from that obvious observation, are there any advantages which one method may offer over the other (especially, in the matter of performance and resources consumption)?
Also, when I read latest books and tutorials about data persistence in Spring Boot, they mainly focus on Spring Data JPA. For example, the 5th edition of "Spring in Action" has no word about Hibernate's EntityMnager. Does it mean that dealing with Hibernate directly can be regarded as kind of "old school" and should generally be avoided in modern projects?
Option #1: Hibernate's EntityManager
#RestController
#RequestMapping(path = "/auth/register", produces = "application/json")
#Transactional
public class RegistrationController {
#PersistenceContext
EntityManager entityManager;
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
public Map<String, String> registerNewUser(#RequestBody #Valid User newUser) {
try {
entityManager.persist(newUser);
entityManager.flush();
} catch (PersistenceException ex) {
// parse exception to find out which constraints have been
// broken - either it's duplicate username, email or both
String message = parseExceptionForConstraintNames(ex);
throw new ResponseStatusException(HttpStatus.CONFLICT, messsage);
}
return Collections.singletonMap("message", "Success...");
}
}
Option #2: custom #Query from CrudRepository
#RestController
#RequestMapping(path = "/auth/register", produces = "application/json")
public class RegistrationController {
private final UsersRepository usersRepository;
#Autowired
public RegistrationController(UsersRepository usersRepository) {
this.usersRepository = usersRepository;
}
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
public Map<String, String> registerNewUser(#RequestBody #Valid User newUser) {
try {
usersRepository.insert(newUser);
} catch (DataIntegrityViolationException ex) {
// parse exception to find out which constraints have been
// broken - either it's duplicate username, email or both
String message = parseExceptionForConstraintNames(ex);
throw new ResponseStatusException(HttpStatus.CONFLICT, message);
}
return Collections.singletonMap("message", "Success...");
}
}
public interface UsersRepository extends CrudRepository<User, String> {
#Modifying
#Transactional
#Query(nativeQuery = true, value = "INSERT INTO users (username, email) " +
"VALUES (:#{#user.username}, :#{#user.email})")
void insert(#Param("user") User newUser);
}
See this answer for Using JPA repository vs Entity Manager.
Best practice is to not use Repository directly. use Service layer between controller and repository where you can implement the logic for duplicate entries by checking if the record already exist in DB using findByUsername(String username); throw exception if it already exist else save() the object in DB
With the given requirements, the username filed in the entity never qualifies for the #Id.
Why can't u add an explicit id field with some sequence generator for the id filed and just keep the username marked with unique constraint only.

Is Spring's QueryByExampleExecutor usable with Projections

Is there a way to return Iterable< IUser > where IUser is a projection of User entity.
Example<User> userExample = Example.of(user, userMatcher);
Iterable<User> foundUsers = userRepository.findAll(userExample, Sort.by("createdAt").descending());
Instead of sticking with spring data auto generated query and response type.
You can create your own HQL query by using #Query annotation so that you can except your custom return type.
Example :
#Query(" select user from User as user where user.userName like %:username% order by createdAt desc")
public List<User> findAllByUserName(String username);

Java REST endpoint to return any database columns

Lets say I have a postgres table named Employee with the following columns:
ID
FirstName
LastName
Employment
Date
Manager
Department
I am interested in having a REST endpoint such that /employee/{ID} will return all information for that particular employee in JSON format, but if I specify /employee/{ID}/FirstName then it'd return particular employee's first name only in JSON format, /employee/{ID}/LastName would return the employee's last name in JSON format, and so on. Is there a good way to implement this instead of implementing an endpoint for accessing each column? Thanks.
A simple way to solve this, is to use a request param instead of querying for the URL. Using a param like fields you would have an URL like /employee/{id}?fields=FirstName,LastName. Using the code below you could have a Map<String, Object> that would be serialized to a JSON with your data. Like this:
#ResponseBody
public Map<String, Object> getPerson(#PathVariable("id") long id, #RequestParam("fields") String fields) throws Exception {
return personService.getPersonFields(id, fields);
}
#Service
class PersonService {
public Map<String, Object> getPersonFields(Long personId, String fields) throws Exception {
final Person person = personRepository.findById(personId);
if (person == null) {
throw new Exception("Person does not exists!");
}
String[] fieldsArray = fields.split(",");
Map<String, Field> personFields = Arrays.stream(person.getClass().getFields()).collect(Collectors.toMap(Field::getName, field -> field);
Map<String, Object> personFieldsReturn = new HashMap<>();
for (String field : fieldsArray) {
if (personFields.containsKey(field)) {
personFields.get(field).setAccessible(true);
personFieldsReturn.put(field, personFields.get(field).get(person));
personFields.get(field).setAccessible(false);
}
}
return personFieldsReturn;
}
}
This is not a good solution though. But it should work.
So as #dave mentioned in a comment you can have a REST endpoint /employee/{ID}/{column} and in your controller you will have a mapping between value of {column} argument and actual column name in database. If you do not want to redeploy your application when mapping changes you can put it in a separate properties file on a server outside of your jar/war and you can also add an additional endpoint to either reload mapping form file on a server or an endpoint that will allow to upload and parse a file with mapping directly to your application.
I would suggest you to use RepositoryRestResource from Spring Data.
First of all, create your entity:
public class Employee {
//props
}
Afterthat create Employee Repository:
#RepositoryRestResource(collectionResourceRel = "employee", path = "employee")
public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {
List<Employee> findByLastName(#Param("firstName") String firstName);
}
And that's all, you will get:
discoverable REST API for your domain model using HAL as media type.
collection, item and association resources representing your mode.
paginating and sorting
and so on.
Check out the docs:
Spring Guide
Spring Dc
Spring Data Rest Project

Change schema of query dynamically based on the connection DEV/SIT/PROD

I have a native query in Spring Data JPA:
#Repository
public interface BookRepository extends JpaRepository<Book, Integer>{
#Query(value = "select * from DEV.Book where find_in_set(market)", nativeQuery = true)
public List<Book> findBooksByMarcket(#Param("market") String market);
}
Now I want to change the DEV.Book based on my connection SIT/PROD dynamically, based on the connection I tried to pass a string into native query which didn't work. What is the best way to do it?
You should set Schema in your connection and leave the query without schema.
You should find some way to remove the namespace from the query.
You could for example have three databases without any namespace. If you configure the database connection via properties you can easily switch by supplying different startup properties or property files.
You can use it in this way:
#Override
public List<Book> findBooksByMarcket(String market) {
TypedQuery query = em.createNativeQuery("select * from DEV.Book where find_in_set(?)", Book.class);
query.setParameter(1, market);
return query.getResultList();
}

How to use createQuery with spring boot

Normally I use annotiations:#Query("SELECT c FROM Country c") with JpaRepositoryor predefined methods like findAll
but in my case I want to generate dynamic query.
String baseQuery =SELECT c FROM Country c`
if(age!=null)
baseQuery+="WHERE c.age=20"
I need to perform same query from code level like this:
Query q1 = em.createQuery("SELECT c FROM Country c");
but I dont use EntityManager in spring boot
How can I generate query from code level?
If you would like to create dynamic queries from code you can take advantage of Spring's JdbcTemplate. Using spring boot it is as simple as injecting JdbcOperations bean to your repository class (assuming you have provided spring-boot-starter-jdbc module to your project).
But remember! This solution uses SQL, not JPQL. That's why you have to use proper tables and columns names in queries and properly map result to objects (i.e. using RowMapper)
This simple example worked fine for me (with different entity, but in same manner - I've adapted it to your example):
#Repository
public class CountryRepository {
#Autowired
private JdbcOperations jdbcOperations;
private static String BASIC_QUERY = "SELECT * FROM COUNTRY";
public List<Country> selectCoutry(Long age){
String query = BASIC_QUERY;
if (age != null){
query += " WHERE AGE = ";
query += age.toString();
}
//let's pretend that Country has constructor Conutry(String name, int age)
return jdbcOperations.query(query, (rs, rowNum) ->
{ return new Country(rs.getString("NAME"), rs.getInt("AGE");}
);
};
}
Then in service or whatever you inject CountryRepository and call method.
Since you're using Spring Boot, you can use Spring Data to create queries in your repository:
#Repository
public interface CountryRepository extends JpaRepository<Country, Long> {
}
Not a 100% on syntax, but should be something similar.
Now you can autowire this class:
#Autowired
public CountryRepository countryRepo;
And all basic methods are already available to you like:
countryRepo.findOne(id);
countryRepo.find();
If you want to make more advanced queries, you can use Spring Data e.g.:
#Repository
public interface CountryRepository extends JpaRepository<Country, Long> {
public Country findByNameAndContinent(String name, String continent);
}
This is just an example (a stupid one) of course and assumes your Country class has the field names 'name' and 'continent' and both are strings. More is available here:
http://docs.spring.io/spring-data/jpa/docs/current/reference/html/
Section 5.3 more specifically.
PS: Make sure your Country class has the #Entity annotation

Categories

Resources