I've an application that use mybatis for object persistence. But there are chances I need to run arbitrary sql(from user). Can I do it with mybatis?
Update:
I choose to use dbutils (JDBC) to run user-defined sql, but I need a instance of DataSource to create QueryRunner. Is there any way I can get datasource from mybatis?
I use this utilitary class:
import java.util.List;
import org.apache.ibatis.annotations.SelectProvider;
public interface SqlMapper {
static class PureSqlProvider {
public String sql(String sql) {
return sql;
}
public String count(String from) {
return "SELECT count(*) FROM " + from;
}
}
#SelectProvider(type = PureSqlProvider.class, method = "sql")
public List<?> select(String sql);
#SelectProvider(type = PureSqlProvider.class, method = "count")
public Integer count(String from);
#SelectProvider(type = PureSqlProvider.class, method = "sql")
public Integer execute(String query);
}
Your question is similar to How to exequte query directly from java code using mybatis?
I have already given the answer to that question. But I hope this solution will help you.
Mybatis has already this function, but you must use the adapter as follows.
create an adapter class;
public class SQLAdapter {
String sql;
public SQLAdapter(String sql) {
this.sql = sql;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
} }
create typeAlias of class SQLAdapter
<typeAlias alias="sqladapter" type="com.zj.xxx.xxx.SQLAdapter" />
put select tag in each object xml where you need to execute the sql directly.
<select id="findRecords" parameterType="SQLAdapter" resultMap="xxxxxResultMap">
${sql}
</select>
call this select method like
String _sql = "select * from table where... order by... limit...";
xxxxx.findRecords(new SQLAdapter(_sql));
Things have been all done. you can no longer write complex sql language in the xml file. Good Luck.
Based on the answers provided, they both are good. But both of them required an Adapter class to be used.
Using Mybatis version 3, I succeeded using a HashMap<String, String> to keep and pass the SQL.
See the codes below.
in Mapper class
final String sql = "${sql}";
#Select(sql)
void execute(HashMap<String, String> m);
when invoke the method:
String sql = "SELECT * FROM record limit 1";
HashMap<String, String> map = new HashMap<String, String>();
map.put("sql", sql);
mapper.execute(map);
HashMap provides a way that you don't have to define the Class properties, or fields in code, you can use a Map to define it redomly.
Thanks.
Reusable fragment of SQL can be used to create select part of query dynamically. In you mapper pass query as normal parameter:
#Param("sql")String sql
In your query just access the parameter using ${sql} instead of #{sql}.
Value in parameter sql can be a fully valid sql query or a fragment of sql query.
For testing I use
import org.apache.ibatis.jdbc.ScriptRunner;
import java.io.Reader;
import java.io.StringReader;
public class test {
private static final String conf = "mybatis.conf.xml";
private SqlSessionFactoryBuilder builder;
private SqlSessionFactory sessionFactory;
Reader reader;
private SqlSession session;
private ScriptRunner runner;
#Before
public void before() {
builder = new SqlSessionFactoryBuilder();
try {
reader = Resources.getResourceAsReader(conf);
} catch (IOException e) {
e.printStackTrace();
}
sessionFactory = builder.build(reader);
session = sessionFactory.openSession();
runner = new ScriptRunner(session.getConnection());
runner.setAutoCommit(true);
runner.setStopOnError(true);
}
#Test
public void testSelectChapelStatus() {
Reader populate = new StringReader("insert into person values (7553,0,'201002496','Wish','Jill','Rain',1,0,NULL,'xxx#LCU.EDU');\r\n"
+ "");
runner.runScript(populate);
}
Related
I need to map the return of a native query to an object
Here is my native query
#Query(value = "select collector from relation;", nativeQuery = true)
Stream<RelationStatistics> findRelationsStatistics();
Here is my object
public class RelationStatistics {
private String collector;
public RelationStatistics(String collector) {
this.collector = collector;
}
public String getCollector() {
return collector;
}
public void setCollector(String collector) {
this.collector = collector;
}
}
Here is my test
#Test
public void test() {
Stream<RelationStatistics> test = relations.findRelationsStatistics();
test.forEach(item -> System.out.println(item));
}
This test return me :
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap] to type [RelationStatistics]
This is an example with only one string attribut but the original native query is a big request so creating an entity will be too difficult.
I have find SqlResultSetMapping but I don't really understand how to use it properly
If someone have an idea of what its possible to do 0_o
I found a solution here : JPA : How to convert a native query result set to POJO class collection
Using projection with an interface as an object with getCollector() method and map with this
like this:
public interface XXXRepository extends CrudRepository<XXX, Integer> {
#Query(value = "select * from ?1 where ...", nativeQuery = true)
List<XXX> findByXXX(String tableName, ...);}
It gives MYSQL syntax error with upon codes.
The syntax error shows that the table name in the SQL is surrounded with "'".
This is not possible. Parameters are only allowed in the where clause.
If you are using a Spring Data repository, you can use #{#entityName} SpEL expression as a placeholder for the entity name. Depending on your use case, it is not necessary to use the entity name as method parameter anymore.
I am not sure if this feature works when you use nativeQuery = true
See the documentation here: https://docs.spring.io/spring-data/data-jpa/docs/current/reference/html/#jpa.query.spel-expressions
You can use EntityManager in JPA project.
#Autowired
EntityManager entityManager;
Query createQuery(String tableName) {
return entityManager.createNativeQuery("select * from " + tableName);
}
I have a workaround.
It uses javax.persistence.EntityManager and String.format to do that.
package com.example.test.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import javax.persistence.EntityManager;
#Component
public class SomeDao {
#Autowired
EntityManager em;
public List<?> listFoodMoneyDateOfPayment(int departmentId, String sumKey, String tableName) {
String s = "SELECT SUM(%s) AS money, CONCAT(YEAR(apply_time), '-', MONTH(apply_time)) AS yearmonth " +
"FROM (%s) WHERE department_id = %d GROUP BY yearmonth";
String sql = String.format(s, sumKey, tableName, departmentId);
System.out.println(sql);
List<?> test = em.createNativeQuery(sql).getResultList();
return test;
}
}
The invoke code is that:
#RestController
#RequestMapping("/api")
public class TestController {
#Autowired
private SomeDao dao;
#RequestMapping("/test2")
public HttpEntity test2() {
var l = dao.listFoodMoneyDateOfPayment(12, "food_payment", "payment_application");
System.out.println(l.getClass());
System.out.println(JSON.toJSONString(l));
return ResultBean.success();
}
}
And it works well.
But you should check the arguments passed in.
This is possible with jdbc:
#Autowired
JdbcTemplate jdbcTemplate;
public String nativeQueryToString(String query) {
try {
return jdbcTemplate.queryForObject(query, String.class);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
I have such question. I'm using DBsetup for spring boot tests and postgresql database. And I'm using DBsetup to set user, but when I'm trying to set another user by spring data I have the next exception:
Подробности: Key (id)=(1) already exists.
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [users_pkey];
This is my test class:
#RunWith(SpringRunner.class)
#SpringBootTest
#TestPropertySource("/application-test.properties")
public class UserRepositoryTest {
#Autowired
private ApplicationContext applicationContext;
#Autowired
private UserRepository userRepository;
#Autowired
private DataSource dataSource;
#Before
public void insertData() throws SQLException {
Operation operation = sequenceOf(CommonOperations.DELETE_ALL, CommonOperations.INSERT_USER);
DbSetup dbSetup = new DbSetup(new DataSourceDestination(dataSource), operation);
dbSetup.launch();
}
#After
public void cleanPK() throws SQLException {
DBUtil.resetAutoIncrementColumns(applicationContext, "user");
}
#Test
public void registerUser() {
val user = new User(null, "Glass", "123123", "glass999#mail.ru");
assertEquals(user, userRepository.saveAndFlush(user));
}
}
Operations for DBsetup:
public class CommonOperations {
public static final Operation DELETE_ALL = deleteAllFrom("article_tag", "article", "tag", "users");
public static final Operation INSERT_USER =
insertInto("users")
.columns("id", "email", "password", "username")
.values(1, "krikkk998#mail.ru", "123123", "Daimon")
.build();
}
Class to reset sequence:
#NoArgsConstructor
public final class DBUtil {
public static void resetAutoIncrementColumns(ApplicationContext applicationContext,
String... tableNames) throws SQLException {
DataSource dataSource = applicationContext.getBean(DataSource.class);
String resetSqlTemplate = "ALTER SEQUENCE %s RESTART WITH 1;";
try (Connection dbConnection = dataSource.getConnection()) {
for (String resetSqlArgument: tableNames) {
try (Statement statement = dbConnection.createStatement()) {
String resetSql = String.format(resetSqlTemplate, resetSqlArgument + "_id_seq");
statement.execute(resetSql);
}
}
}
}
}
Does anyone know how to solve this problem?
One thing to look at:
public static final Operation INSERT_USER =
insertInto("users")
.columns("id", "email", "password", "username")
.values(1, "krikkk998#mail.ru", "123123", "Daimon")
.build();
Here you are using a hard-coded id i.e. 1
Now, when in the test case you are trying to create another user, you passed the id as null, assuming it's supposed to pick from the sequence. It will start from 1 too. Hence, you get a conflict.
You have an issue related to constraint violation. So one thing you can do is in your table change the 'id' column to "auto_increment". The DB will take care of incrementing this column value automatically.
At any point, if you want to reset this id value, you can call "resetAutoIncrementColumns()", and then in your INSERT SQL, you do not have to specify the 'id' column at all. It will always insert a unique value when a new user is saved.
Hope this will help you.
How can we write mockito for the below code? It's been written in normal JDBC. I need to create a mock of all this code having main method (which is driving all the logic of updating the data).
I am really need help in mocking the avoid inserting the actual data. Could someone please guide me ?
public class PaytPaytmBilling {
private static Category logger = Category.getInstance(PaytPaytmBilling.class);
private static InputStream inputS = XY.class.getResourceAsStream("/paytm.properties");
private static final INSERT_QUERY = "INSERT STATEMENT";
private static void insertPaytPaytmBilling(ArrayList allPaytPaytmBill) throws Exception{
conn = getConnection(userId, passwd, prop.getProperty("databaseURL"));
String childSql = buildInsertPaytPaytmBillSql();
PreparedStatement pStatement = conn.prepareStatement(childSql);
for (int i=0; i<allPaytPaytmBill.size(); i++){
PaytPaytmBill PaytmBill = (PaytPaytmBill) allPaytPaytmBill.get(i);
pStatement.setString(1, PaytmBill.getXX());
pStatement.setString(2, PaytmBill.getYY());
pStatement.setString(3, PaytmBill.getAA());
pStatement.setLong(4, PaytmBill.getBB());
pStatement.setLong(5, PaytmBill.getCC));
pStatement.setString(6, PaytmBill.getDD());
pStatement.setInt(7, PaytmBill.getEE());
pStatement.setInt(8, PaytmBill.getFF());
pStatement.setString(9, "");
pStatement.setString(10, "");
pStatement.execute();
}
pStatement.close();
conn.close();
}
private static void getDbConn() throws Exception {
// Here get DB connection
}
public static void main(String[] args) throws Exception
{
ArrayList allPaytPaytmBill = new ArrayList();
XY.init();
getDbConn();
// This query reads data from other tables and creates the data..
String qmrString = qmr.buildQmrSql();
allPaytPaytmBill = qmr.getAllMemberData(qmrString);
insertPaytPaytmBilling(allPaytPaytmBill);
}
}
Mockito Test class:
#RunWith(MockitoJUnitRunner.class)
public class PaytmBillingTest {
private static Category logger = Category.getInstance(PaytmBillingTest.class);
#Mock
private DataSource ds;
#Mock
private Connection c;
#Mock
private PreparedStatement stmt;
#Mock
private ResultSet rs;
private ArrayList<PaytmBill> allPaytmBill;
#Before
public void before() {
allPaytmBill = new ArrayList<>();
PaytmBill PaytmBill = new PaytmBill();
PaytmBill.setAA("1182");
PaytmBill.setBB("5122");
PaytmBill.setCC("201807");
PaytmBill.setDD(0L);
PaytmBill.setEE(100);
PaytmBill.setFF(0);
PaytmBill.setGG(0);
PaytmBill.setHH("A");
PaytmBill.setII(null);
PaytmBill.setJJ(null);
allPaytmBill.add(PaytmBill);
}
#Test
public void testPaytmBilling() {
PaytmBilling PaytmBilling = new PaytmBilling();
}
}
First of all, it looks like you are not showing use the real code. For example you added private static void getDbConn() but the code calls conn = getConnection(...), the variable conn is not declared anywhere, etc. This makes it harder to really help with your issue.
Looking at your unit test, you want to mock instances of certain classes used by PaytPaytmBilling, like DataSource, Connection and PreparedStatement. These are called 'dependencies'.
In order to do that, you need to change PaytPaytmBilling so that these dependencies are 'injected' (see Dependency Injection). This means they are provided to PaytPaytmBilling via the constructor or a setter (or with some frameworks just by adding an annotation on the field).
In the current code, the dependencies are obtained by PaytPaytmBilling itself (e.g. by calling a static method, or creating a new instance) and they cannot be mocked (except via some black magic mocking frameworks which I don't advise you to get into right now).
To write good unit tests, you need to write (or refactor) the code to be testable, which means dependencies are injected, not obtained internally in the class. Also avoid static methods and data (constants are ok), they don't play nice with dependency injection and testable code.
So for example the DataSource could be injected via the constructor like this:
public class PaytPaytmBilling {
private static final String CHILD_SQL = "SELECT bladiebla...";
private DataSource dataSource;
public PaytPaytmBilling(DataSource dataSource) {
this.dataSource = dataSource;
}
public void insertPaytPaytmBilling(List<PaytmBill> allPaytPaytmBill) {
// keeping the example simple here.
// don't use String literals for the parameters below but read
// them from Properties (which you can mock for the unit test)
Connection conn = dataSource.getConnection("userId", "passwd", "url");
PreparedStatement pStatement = conn.prepareStatement(CHILD_SQL);
for (int i=0; i<allPaytPaytmBill.size(); i++){
PaytPaytmBill PaytmBill = (PaytPaytmBill) allPaytPaytmBill.get(i);
pStatement.setString(1, PaytmBill.getXX());
pStatement.setString(2, PaytmBill.getYY());
pStatement.setString(3, PaytmBill.getAA());
// ...
pStatement.execute();
}
pStatement.close();
conn.close();
}
If you re-write the code like above, you could test it like this:
#RunWith(MockitoJUnitRunner.class)
public class PaytmBillingTest {
// this will cause Mockito to automatically create an instance
// and inject any mocks needed
#InjectMocks
private PaytmBilling instanceUnderTest;
#Mock
private DataSource dataSource;
// connection is not directly injected. It is obtained by calling
// the injected dataSource
#Mock
private Connection connection;
// preparedStatement is not directly injected. It is obtained by
// calling the connection, which was obtained by calling the
// injected dataSource
#Mock
private PreparedStatement preparedStatement;
private List<PaytmBill> allPaytmBill;
#Before
public void before() {
allPaytmBill = new ArrayList<>();
PaytmBill paytmBill = new PaytmBill();
paytmBill.setAA("1182");
paytmBill.setBB("5122");
paytmBill.setCC("201807");
paytmBill.setDD(0L);
paytmBill.setEE(100);
paytmBill.setFF(0);
paytmBill.setGG(0);
paytmBill.setHH("A");
paytmBill.setII(null);
paytmBill.setJJ(null);
allPaytmBill.add(PaytmBill);
}
#Test
public void testPaytmBilling() {
// given
when(dataSource.getConnection(anyString(), anyString(), anyString())).thenReturn(connection);
when(connection.prepareStatement(anyString())).thenReturn(preparedStatement);
// when
instanceUnderTest.insertPaytPaytmBilling(allPaytPaytmBill);
// then
verify(pStatement).setString(1, paytmBill.getXX());
verify(pStatement).setString(2, paytmBill.getYY());
verify(pStatement).setString(3, paytmBill.getAA());
// ...
verify(pStatement).execute();
verify(pStatement).close();
verify(connection).close();
}
Unrelated suggestion regarding your code: It's better to close resources in a finally block, or using try-with resources. In you current code resources will not be closed if an exception occurs whilst processing on the resources:
Connection conn = dataSource.getConnection("userId", "passwd", "url");
PreparedStatement pStatement = conn.prepareStatement(childSql);
try {
// processing steps
}
finally {
pStatement.close();
conn.close();
}
Or try-with-resources:
try (Connection conn = dataSource.getConnection("userId", "passwd", "url"),
PreparedStatement pStatement = conn.prepareStatement(childSql)) {
// processing steps
}
Since Connection and PreparedStatement implement the AutoCloseable interface they will be closed automatically when the try block ends. This is possible since Java 7.
I have configured a custom generic service DAO for my spring / hibernate project - the idea being that I can reuse it easily from my controllers.
It essentially looks like this:
public class DefaultService<T> {
private Class<T> e;
public String className(Class<T> e) {
String clip = e.getName();
clip = clip.substring(clip.lastIndexOf('.') + 1, clip.length());
return clip;
}
public List<T> getAll(Integer status) {
Session session = sessionFactory.getCurrentSession();
Query query = session.createQuery("FROM " + className(e) + " WHERE status = " + status);
return query.list();
}
...
Which gets referenced by:
#Autowired
public DefaultService<Address> addressService;
addressService.get(1);
However the String clip = e.getName() line throws a Null pointer exception. I can get this to work if I move the class into the attributes section (so addressService.get(Address.class, 1) but I find this somewhat untidy, especially when there are multiple different classes being called upon.
Is there some way to get the class to generate a value correctly without repeatedly adding it into all my functions?
Thanks in advance.
I did something similar, you need the generic class to be a constructor argument as well, mine uses hibernate entities, but you could pass in the string of table name.
public class DomainRepository<T> {
#Resource(name = "sessionFactory")
protected SessionFactory sessionFactory;
public DomainRepository(Class genericType) {
this.genericType = genericType;
}
#Transactional(readOnly = true)
public T get(final long id) {
return (T) sessionFactory.getCurrentSession().get(genericType, id);
}
You can then subclass (if you need to) to customize or simply set up you bean in the spring config like below t :
<bean id="tagRepository" class="com.yourcompnay.data.DomainRepository">
<constructor-arg value="com.yourcompnay.domain.Tag"/>
</bean>
So in your code you could then reference tagRepository like so (no other cod eis needed than that posted above, and below) :
#Resource(name = "tagRepository")
private DomainRepository<Tag> tagRepository;
Also, I would call it a repository not a service, a service deals with different types and their interactions (not just one). And for specifically your example using SQL strings :
public final String tableName;
public DomainRepository(String tableName) {
this.tableName = tableName;
}
public List<T> getAll(Integer status) {
Session session = sessionFactory.getCurrentSession();
Query query = session.createQuery("FROM " + tableName + " WHERE status = " + status);
return query.list();
}
and have your beans defined like so
<bean id="addressRepository" class="com.yourcompnay.data.DomainRepository">
<constructor-arg value="address"/>
</bean>
And then you can alsow create subclasses youself where necessary :
public class PersonRepository extends DomainRepository<Person> {
public PersonRepository(){
super("person"); //assumes table name is person
}
As I understand you got NPE because you did not set any value for this field.
So you can resolve this problem by 2 ways:
Set manually class object as in comment NimChimpsky.
Get class type dynamically. E.g, if you use Spring try this one:
protected Class getEntityClass() {
return GenericTypeResolver.resolveTypeArguments(getClass(), DefaultService.class)[0];
}
or some workaround here
It's better to define a specific class for Address service
public class AddressService extends DefaultService<Address>{
public String getClassName(){
return "Address";
}
}
where
public String getClassName();
is an abstract method declared in DefaultService, and used (like your method className()) in your data access logic.
Using this approach, you will be able to add specific data access logic (example, getUsersByAddress)