Let's say I have to fire a query like this:
Select primarykey, columnname, old_value, new_value from first_audit_log;
Select primarykey, columnname, old_value, new_value from second_audit_log;
Select primarykey, columnname, old_value, new_value from third_audit_log; ...so on
audit_log is not mapped as JPA enity to any class and I strictly can't create n number of classes for n number of *_audit_logs.
Using native query feature, how best I can map this to a generic class? Trying to SELECT NEW feature, but not sure... Hence any help is appreciated.
Since your audit logs tables share the same columns, you can create a view that "unifies" those tables and map a single Java class to that view. I believe you can, since you don't need to write updates, I guess.
As an alternative, using native queries would be a good choice.
EDIT:
1) If your audit logs are already views, you can create a view based on other views, if you don't want to create a mapping Java class for each of them. Just remember to add a dummy column that has value 1 if the row comes from the "first" audit log, 2 if it comes from the second, and so on, so you can set them apart.
2) In order to use native queries, assuming your persistence provider is Hibernate, you can do like in this example:
EntityManagerFactory emf = Persistence.createEntityManagerFactory("test");
EntityManager em = emf.createEntityManager();
Session sess = em.unwrap(Session.class); // <-- Use Hibernate-specific features
SQLQuery query = sess.createSQLQuery(
"SELECT AVG(age) AS averageAge, AVG(salary) as averageSalary FROM persons");
query.setResultTransformer(Transformers.aliasToBean(MyResult.class));
MyResult result = (MyResult) query.list().get(0);
where MyResult is declared as follows:
public class MyResult {
private BigDecimal averageAge;
private BigDecimal averageSalary;
public BigDecimal getAverageAge() {
return averageAge;
}
public void setAverageAge(BigDecimal averageAge) {
this.averageAge = averageAge;
}
public BigDecimal getAverageSalary() {
return averageSalary;
}
public void setAverageSalary(BigDecimal averageSalary) {
this.averageSalary = averageSalary;
}
}
and the persons table is like this (MySQL syntax):
CREATE TABLE `persons` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`firstname` varchar(255) NOT NULL,
`lastname` varchar(255) NOT NULL,
`age` int(11) NOT NULL,
`salary` int(11) NOT NULL,
PRIMARY KEY (`id`)
);
You can easily adapt this example to your needs, just replace persons and MyResult with what you want.
The aliases in the sql query is automatically converted to upper case and its looking for the setter in Upper case as a result org.hibernate.PropertyNotFoundException Exception is thrown. Any suggestions would be greatly appreciated.
For instance, the below statement is looking for the setter ID instead of Id/id (Could not find setter for ID on class Data)
List<Data> result = entityManager.unwrap(Session.class)
.createSQLQuery("Select id as id from table")
.setParameter("day", date.getDayOfMonth())
.setParameter("month", date.getMonthOfYear())
.setParameter("year", date.getYear())
.setResultTransformer(Transformers.aliasToBean(Data.class))
.list();
class Data {
Integer id;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
Related
i have the following TableStructure in a PostgreSQL DB which is supposed to be the DB Backend for my WebApp:
init_db.sql
CREATE TABLE article (
id integer NOT NULL,
name character varying NOT NULL,
type_id integer NOT NULL
);
CREATE TABLE article_type (
id integer NOT NULL,
type_desc character varying NOT NULL
);
ALTER TABLE ONLY article
ADD CONSTRAINT
article_type_id_fkey FOREIGN KEY (type_id) REFERENCES article_type(id);
The basic access to this works (via DataSource Object defined in application.properties and letting Spring Boot handle the rest). I'm having now difficulties in understanding how to access/model this best in Spring Boot. Currently my Model Classes look like this:
ArticleType.java
public class ArticleType {
private Integer id;
private String name;
// Getters and Setters
}
andArticle.java
public class Article {
private Integer id;
private String name;
private String desc;
private ArticleType article_type;
// Getters and Setters
}
Following this example, i was constructing those classes:
ArticleTypeRepository.java
#Repository
public class ArticleTypeRepository {
#Autowired
protected JdbcTemplate jdbc;
public ArticleType getArticleType(int id) {
return jdbc.queryForObject("SELECT * FROM article.article_type WHERE id=?", articleTypeMapper, id);
}
private static final RowMapper<ArticleType> articleTypeMapper = new RowMapper<ArticleType>() {
public ArticleType mapRow(ResultSet rs, int rowNum) throws SQLException {
ArticleType articletype = new ArticleType();
articletype.setId(rs.getInt("id"));
articletype.setName(rs.getString("type_desc"));
return articletype;
}
};
and for the following file my question arises:ArticleRepository.java
#Repository
public class ArticleRepository {
#Autowired
protected JdbcTemplate jdbc;
public Article getArticle(int id) {
return jdbc.queryForObject("SELECT * FROM article.article WHERE id=?", articleMapper, id);
}
private static final RowMapper<Article> articleMapper = new RowMapper<Article>() {
public Article mapRow(ResultSet rs, int rowNum) throws SQLException {
Article article = new Article();
article.setId(rs.getInt("id"));
article.setName(rs.getString("name"));
// The following line is the one in question
// ArticleType at = getArticleType(Integer.parseInt(rs.getString("type_id")));
article.setArticle_type(at);
article.setDesc(rs.getString("description"));
return article;
}
};
What is the best practice to get the ArticleType here for the Article? Is this anyway good practice to retrieve those objects? Or should I just use a plain String object in the Article Object and query this with a view or something? I looked through the internet for "Spring Boot JDBC Nested Object Java Access Modeling" and the alike, but couldn't find any real hints or tutorials to this specific question, which makes me wonder if i'm doing something conceptually completely wrong. Any hints are appreciated (tutorials, doc's, paradigms how to do this properly, etc.)
I'll double post M. Deinum 's answer here, since it got me rolling until i switched to Hibernate/JPA:
By creating a query that returns everything you need. Write a select
statement that joins both tables.
I'm wondering if there's a possibility to fetch multiple tables to only one java class
for exemple :
TABLE LABELS;
TABLE STANDARDS;
TABLE REFERENCES;
mapped to the same class
public Class Information {
private String type; // the type is the element who have to do the mapping => LABELS/STANDARDS/REFERENCES
...
}
It's not possible for me to construct one class for each type for technical reason (I known that some heritage should be cool).
Thank you
Gilles
EDIT :
I'll try to expain a bit more :)
I'm using a JMS service to get the informations. Each message have a particulary type, (in my exemples : "labels","standards" and "references").
By using those type, I want to persit the informations in the respective Tables. The structure is exactly the same for every messages, it's why I wanna use a unique POJO.
I hope it was better explain :)
EDIT 2 :
TABLE LABELS (
ID PRIMARY KEY AUTO_INCREMENT,
MESSAGE VARCHAR(255),
AUTHOR VARCHAR(255)
);
TABLE STANDARDS(
ID PRIMARY KEY AUTO_INCREMENT,
MESSAGE VARCHAR(255),
AUTHOR VARCHAR(255)
);
TABLE REFERENCES (
ID PRIMARY KEY AUTO_INCREMENT,
MESSAGE VARCHAR(255),
AUTHOR VARCHAR(255)
);
and here's some examples of JMS
headers :
type : label
body:
{message:"name of the country",author:"john doe"}
headers :
type : label
body:
{message:"nom du pays",author:"jenny doe"}
headers :
type : reference
body:
{message:"country",author:"john doe"}
and I want to put them into the Information Class and persist them into the correct Table
Try this:
#MappedSuperclass
public class Base {
private String message;
private String autor;
#Column(name = "MESSAGE")
public String getMessage(){
return message;
}
public void setMessage(final String message) {
this.message = message;
}
#Column(name = "AUTOR")
public String getAutor(){
return autor;
}
public void setAutor(final String autor) {
this.autor = autor;
}
}
And three classes:
#Entity
#Table(name="LABELS")
public class Labels extends Base{};
And
#Entity
#Table(name="STANDARDS")
public class Standards extends Base{};
And
#Entity
#Table(name="REFERENCES")
public class References extends Base{};
Now you can persist the data using:
Base b;
if (info.getType().equals("REFERENCES")) {
b=new References();
} else if (info.getType().equals("LABELS")) {
b=new Labels();
} else if (info.getType().equals("STANDARDS")) {
b=new Standards();
} else {
return;
}
b.setMessage(info.getMessage());
b.setAutor(info.getAutor());
Transaction t = session.beginTransaction();
session.persist(b);
t.commit();
You can use secondaryTables:
http://docs.oracle.com/javaee/6/api/javax/persistence/SecondaryTables.html
You might be asking if you can do a table per subclass mapping. You can have one class per table, but all inherit from a common base class.
Alternately, if you're building a read-only mapping, you could make a view that is a UNION over all the relevant tables, and just map the view using Hibernate. Something like
-- SQL
CREATE VIEW information AS
SELECT 'LABEL' AS type,... FROM labels
UNION
SELECT 'STANDARD',... FROM standards
UNION
SELECT 'REFERENCE',... FROM references
;
/* Java */
#Entity
#Table(name="information")
public class Information {
...
}
The below gives an error. And all my searching indicates that the table name needs to be hardcoded. But possibly there is some clever workaround I couldn't find?
public interface MyDao {
#SqlQuery("INSERT INTO :tbl (ID) VALUES (:id)")
void logInserts1817(#Bind("tbl") String tbl, #Bind("id") String id);
}
I have a mysql table like this:
CREATE TABLE `sezione_menu` (
`id_sezione_menu` int(11) unsigned NOT NULL AUTO_INCREMENT,
`nome` varchar(256) NOT NULL DEFAULT '',
`ordine` int(11) DEFAULT NULL,
PRIMARY KEY (`id_sezione_menu`)
)ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
I use apache dbutils to query my database, with these methods:
public static List<SezioneMenu> getSezioniMenu() {
String sql = "SELECT * FROM sezione_menu";
try {
QueryRunner qr = new QueryRunner(createDataSource());
ResultSetHandler rsh = new BeanListHandler(SezioneMenu.class);
List<SezioneMenu> sezioni = (List<SezioneMenu>)qr.query(sql, rsh);
return sezioni;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
private static DataSource createDataSource() {
BasicDataSource d = new BasicDataSource();
d.setDriverClassName(DRIVER);
d.setUsername(USERNAME);
d.setPassword(PASSWORD);
d.setUrl(DB_URL);
return d;
}
Now, if i run my application, it doesn't throw exception, but some fields (not all!) of my java bean SezioneMenu are empty (integer field equals zero and string field equals empty string).
This happen also with other tables and beans.
I used this method in the past in another system configuration without problems.
You can fix it in two ways:
As per dbutils doc,
Alias the column names in the SQL so they match the Java names: select social_sec# as socialSecurityNumber from person
Subclass BeanProcessor and override the mapColumnsToProperties() method to strip out the offending characters.
If you are keeping a class like this
public class SezioneMenuBean implements Serializable {
private int idSezioneMenu;
private String nome;
private int ordine;
public SezioneMenuBean() {
}
// Getters and setters for bean values
}
As per first solution write your queries something like this SELECT id_sezione_menu AS idSezioneMenu, name, ordine FROM sezione_menu.
Or
Based on second solution you can use GenerousBeanProcessor which is a subclass of BeanProcessor it ignores underscore & case sensitivity from column name. You don't have to implement your own custom BeanProcessor
GenerousBeanProcessor is available since version 1.6 of commons-dbutils.
Usage:
// TODO initialize
QueryRunner queryRunner = null;
ResultSetHandler<List<SezioneMenuBean>> resultSetHandler =
new BeanListHandler<SezioneMenuBean>(SezioneMenuBean.class, new BasicRowProcessor(new GenerousBeanProcessor()));
// best practice is specifying only required columns in the query
// SELECT id_sezione_menu, name, ordine FROM sezione_menu
final List<SezioneMenuBean> sezioneMenuBeans = queryRunner.query("SELECT * FROM sezione_menu", resultSetHandler);
for (SezioneMenuBean sezioneMenuBean : sezioneMenuBeans) {
System.out.println(sezioneMenuBean.getIdSezioneMenu());
}
I faced the same issue of BeanHandler/BeanHandlerList returning null or 0 for database columns.
As mentioned by #aelfric5578 in the comment, I have updated the Bean class with same names as Database, DBUtils returned values correctly.
Having BeanClass defined like this will solve your problem.
public class SezioneMenuBean{
int id_sezione_menu;
String nome;
int ordine;
public SezioneMenuBean(){
}
// Getters and setters for bean values
}
I am trying to implement MyBatis in my project at work. It is a legacy system, which uses vanilla JDBC to access the database, solely through stored procedures. I understand that to call a stored procedure, MyBatis requires an object which contains the input parameters for the stored procedure and another that will hold the result set. Not sure if this is entirely true.
To prevent creating too many data entities in the system, I want to reuse the existing ones. And here is where the problem arises. Let me explain what the typical situation/scenario I am facing, and then how I am trying to solve it.
Let's say I have the following data entity(ies) in the system:
class Account {
private int accountID;
private String accountName;
private OrganizationAddress address;
// Getters-Setters Go Here
}
class OrganizationAddress extends Address {
// ... some attributes here
// Getters-Setters Go Here
}
class Address {
private String address;
private String city;
private String state;
private String country;
// Getters-Setters Go Here
}
I am using annotations, so my Mapper class has something like this:
#Select(value = "{call Get_AccountList(#{accountType, mode=IN, jdbcType=String})}")
#Options(statementType = StatementType.CALLABLE)
#Results(value = {
#org.apache.ibatis.annotations.Result
(property = "accountID", column = "Account_ID"),
#org.apache.ibatis.annotations.Result
(property = "accountName", column = "Organization_Name"),
#org.apache.ibatis.annotations.Result
(property = "state", column = "State", javaType=OrganizationAddress.class)
})
List<Account> getAccountList(Param param);
Problem: When I make the call to the stored procedure, the Account object has the state always null.
To add to the injury, I do not have access to the source of the above data entities. So I couldn't try the solution provided on this link either - Mybatis select with nested objects
My query:
Is it possible for me to use the data entites already present in the system, or do I have to create new ones, and then map the data to the existing ones?
If yes, how do I go about it? Any references, if any.
If no, is there a way to reduce the number of data entities I would create to call the stored procedures (for both in and out parameters)?
I think the best solution for your situation (if I understand it correctly) is to use a MyBatis TypeHandler that will map the state column to an OrganizationAddress object.
I've put together a example based on the information you provided and it works. Here is the revised annotated Mapper:
// Note: you have an error in the #Select line => maps to VARCHAR not "String"
#Select(value = "{call Get_AccountList(#{accountType, mode=IN, jdbcType=VARCHAR})}")
#Options(statementType = StatementType.CALLABLE)
#Results(value = {
#org.apache.ibatis.annotations.Result
(property = "accountID", column = "Account_ID"),
#org.apache.ibatis.annotations.Result
(property = "accountName", column = "Organization_Name"),
#org.apache.ibatis.annotations.Result
(property = "address", column = "State", typeHandler=OrgAddressTypeHandler.class)
})
List<Account> getAccountList(Param param);
You need to map the address field of Account to the "state" column and use a TypeHandler to create an OrganizationAddress with its "state" property filled in.
The OrgAddressTypeHandler I created looks like this:
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
public class OrgAddressTypeHandler extends BaseTypeHandler<OrganizationAddress> {
#Override
public OrganizationAddress getNullableResult(ResultSet rs, String colName) throws SQLException {
OrganizationAddress oa = new OrganizationAddress();
oa.setState(rs.getString(colName));
return oa;
}
#Override
public OrganizationAddress getNullableResult(ResultSet rs, int colNum) throws SQLException {
OrganizationAddress oa = new OrganizationAddress();
oa.setState(rs.getString(colNum));
return oa;
}
#Override
public OrganizationAddress getNullableResult(CallableStatement cs, int colNum) throws SQLException {
OrganizationAddress oa = new OrganizationAddress();
oa.setState(cs.getString(colNum));
return oa;
}
#Override
public void setNonNullParameter(PreparedStatement arg0, int arg1, OrganizationAddress arg2, JdbcType arg3) throws SQLException {
// not needed for this example
}
}
If you need a more complete working example than this, I'll be happy to send more of it. Or if I have misunderstood your example, let me know.
With this solution you can use your domain objects without modification. You just need the TypeHandler to do the mapping and you don't need an XML mapper file.
Also I did this with MyBatis-3.1.1 in MySQL. Here is the simple schema and stored proc I created to test it:
DROP TABLE IF EXISTS account;
DROP TABLE IF EXISTS organization_address;
CREATE TABLE account (
account_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
organization_name VARCHAR(45) NOT NULL,
account_type VARCHAR(10) NOT NULL,
organization_address_id SMALLINT UNSIGNED NOT NULL,
PRIMARY KEY (account_id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE organization_address (
organization_address_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
address VARCHAR(45) NOT NULL,
city VARCHAR(45) NOT NULL,
state VARCHAR(45) NOT NULL,
country VARCHAR(45) NOT NULL,
PRIMARY KEY (organization_address_id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO organization_address VALUES(1, '123 Foo St.', 'Foo City', 'Texas', 'USA');
INSERT INTO organization_address VALUES(2, '456 Bar St.', 'Bar City', 'Arizona', 'USA');
INSERT INTO organization_address VALUES(3, '789 Quux Ave.', 'Quux City', 'New Mexico', 'USA');
INSERT INTO account VALUES(1, 'Foo', 'Type1', 1);
INSERT INTO account VALUES(2, 'Bar', 'Type1', 2);
INSERT INTO account VALUES(3, 'Quux', 'Type2', 3);
DROP PROCEDURE IF EXISTS Get_AccountList;
DELIMITER $$
CREATE PROCEDURE Get_AccountList(IN p_account_type VARCHAR(10))
READS SQL DATA
BEGIN
SELECT a.account_id, a.organization_name, o.state
FROM account a
JOIN organization_address o ON a.organization_address_id = o.organization_address_id
WHERE account_type = p_account_type
ORDER BY a.account_id;
END $$
DELIMITER ;