在上一篇裏,咱們已經取到了咱們在生成sql語句中所須要的信息,這一篇裏咱們開始根據class來生成咱們須要的sql。在這以前咱們先確認幾件事情java
sql裏的參數咱們使用佔位符的形式。git
這裏用的是jdbc中的PreparedStatement,sql中的參數使用「?」的形式。github
大體上是這樣的:spring
Connection connection = dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("select * from `user` where `status` = ? ;");
preparedStatement.setObject(1, 0);
ResultSet resultSet = preparedStatement.executeQuery();
複製代碼
可是這樣的話咱們每次執行都須要手寫這些執行sql的繁瑣的代碼,我在這裏選擇使用spring-jdbc中的JdbcTemplte。這樣我就只須要生成sql,而後使用JdbcTemplte裏的方法來執行sql就行了。sql
咱們只生成單表的增刪改查,不涉及複雜sql。數據庫
不貼出完整的代碼,以說明思路爲主。數組
畢竟這個是已經寫好的代碼,地址在:github.com/hjx60149632… 。全部代碼能夠在這裏找到。app
咱們主要解決的是增刪該查的問題,因此咱們先寫如何生成一個新增的sql。less
我麼先觀察一下sql通常來講都有什麼構成。如今先放一個例子出來:ide
insert
INSERT INTO user (name, id, create_date, age, mark, status)
VALUES (?, ?, ?, ?, ?, ?);
複製代碼
delete
DELETE
FROM user
WHERE id = ?
複製代碼
update
UPDATE user
SET name = ?,
id = ?,
create_date = ?,
age = ?,
status = ?
WHERE id = ?
複製代碼
select
SELECT name, id, create_date, age, mark, status
FROM user
WHERE id = ?
複製代碼
經過觀察上面的sql,能夠發現其中有一些共性:
接下來,就能夠按照每種類型的sql來建立sql了。
一下全部的對象都是這個User.java
import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
@Table(name = "user")
public class User {
@Column(name = "name")
private String name;
@Id
@Column(name = "id")
private int id;
@Column(name = "age")
private int age;
@Column(name = "mark")
private String mark;
@Column(name = "create_date")
private Date createDate;
@Column(name = "status")
private int status;
// getter setter toString
}
複製代碼
主要用來操做字符串
import java.util.Collection;
import java.util.Iterator;
/** * @author hjx */
public class StringUtils {
public static final String SPACE = " ";
public static final String BLANK = "";
public static final String COMMA = ", ";
/** * 重複字符串 * * @param str * @param number * @return */
public static String[] repeat(String str, int number) {
Assert.notNull(str);
String[] strings = new String[number];
for (int i = 0; i < number; i++) {
strings[i] = str;
}
return strings;
}
/** * 組合字符串 * * @param strings * @return */
public static String append(final Object... strings) {
StringBuilder builder = new StringBuilder();
for (Object s1 : strings) {
if (s1 == null) {
continue;
}
builder.append(s1.toString());
}
return builder.toString();
}
/** * 組合字符串 * * @param collection * @param separator * @return */
public static String join(Collection collection, String separator) {
StringBuffer var2 = new StringBuffer();
for (Iterator var3 = collection.iterator(); var3.hasNext(); var2.append((String) var3.next())) {
if (var2.length() != 0) {
var2.append(separator);
}
}
return var2.toString();
}
}
複製代碼
用來從對象中取值的,使用反射。
/** * 取值 * * @param target 要從哪個對象中取值 * @param field 要取這個對象的那個屬性的值 * @return */
public static Object getValue(Object target, Field field) {
//忽略掉private
field.setAccessible(true);
try {
return field.get(target);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
複製代碼
用來給對象設置值的,仍是反射。
/** * 設置值 * * @param target 要從哪個對象中取值 * @param field 要取這個對象的那個屬性的值 * @param value 要設置的值 * @return */
public static boolean setValue(Object target, Field field, Object value) {
field.setAccessible(true);
try {
field.set(target, value);
return true;
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return false;
}
複製代碼
下面就能夠開始建立各類sql了~~~
新增的sql仍是比較好實現的,咱們須要的大體就是:
首先咱們要根據User.java拿到全部的表的字段個名稱,和對應的值。就是上一篇寫到的:EntityTableRowMapper
拿到字段和class屬性的值
Map<String, Field> columnFieldMapper = entityTableRowMapper.getColumnFieldMapper();
insertColumns = new ArrayList(columnFieldMapper.size());
for (Map.Entry<String, Field> stringFieldEntry : columnFieldMapper.entrySet()) {
Field field = stringFieldEntry.getValue();
Object value = EntityUtils.getValue(entity, field);
if (value == null) {
continue;
}
insertColumns.add(stringFieldEntry.getKey());
insertColumnValues.add(value);
}
複製代碼
這裏有兩個變量:
insertColumns:sql中的字段名。
insertColumnValues:sql中的字段對應的值。
生成插入的sql:
StringBuilder builder = new StringBuilder();
int size = insertColumns.size();
builder.append("INSERT INTO ").append(getTableName()).append(StringUtils.SPACE);
builder.append(StringUtils.append("( ", StringUtils.join(insertColumns, ", "), " ) "));
builder.append("VALUES ");
for (int i = 0; i < insertCount; i++) {
builder.append("( ");
String[] repeat = StringUtils.repeat("?", size);
builder.append(StringUtils.join(Arrays.asList(repeat), ", "));
builder.append(" )");
if (i != insertCount - 1) {
builder.append(StringUtils.COMMA);
}
}
builder.append(";");
複製代碼
生成的結果:
//user
User user = new User();
user.setId(10);
user.setCreateDate(new Date());
user.setAge(20);
user.setMark("ceshi");
user.setName("heiheihei");
//sql
INSERT INTO user ( name, id, create_date, age, mark, status ) VALUES ( ?, ?, ?, ?, ?, ? );
//value
[heiheihei, 10, Tue Jan 22 16:33:00 CST 2019, 20, ceshi, 0]
複製代碼
如今能夠拿着生成的sql和值去執行啦~
jdbcTemplate.update(sql, insertColumnValues.toArray());
複製代碼
上一篇裏咱們實現了生成insert的sql,下面要開始實現update,delete,select的sql語句了。可是這些語句有一個比較麻煩的地方是:它們通常後面都會有where條件,由於在執行的時候不能把表裏全部的數據都進行操做。
因此這裏咱們須要先生成條件的sql。大概是這樣的:
WHERE id = ? AND name != ? OR age >= ?
複製代碼
where 後面的參數繼續用 「?」 代替。值就放在一個有序的集合中就行了。相似上一篇提到的insertColumnValues。
咱們實現第一步,在這以前咱們先看一下一個條件是有什麼組成的,例如:
1: id = ? AND
2: name != ? OR
3: age >= ?
複製代碼
這裏經過觀察能夠發現,每個條件都是由一個 字段名稱,一個判斷,**一個佔位符 "?"**和後面用於鏈接條件的 AND 或者 OR 所構成。這樣咱們能夠編寫一個類用來保存這些信息:
Where.java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/** * where條件 默認使用 and 鏈接多個條件 * * @author hjx */
public class Where {
protected static final String PLACEHOLDER = "#{COLUMN}";
static final String AND = "AND ";
static final String OR = "OR ";
private String sql;
private String column;
private String connect = AND;
private List<Object> values;
/** * 是否有值(null 也表明有值) */
private boolean hasValue;
/** * @param column 被操做的列 * @param sql 操做的sql */
public Where(String column, String sql) {
this.column = column;
this.sql = sql;
this.hasValue = false;
this.values = new ArrayList<>();
}
/** * @param column 被操做的列 * @param sql 操做的sql * @param value sql的參數 */
public Where(String column, String sql, Object value) {
this.sql = sql;
this.column = column;
this.values = new ArrayList<>();
this.values.add(value);
this.hasValue = true;
}
/** * @param column 被操做的列 * @param sql 操做的sql * @param values sql的參數 */
public Where(String column, String sql, Object[] values) {
this.sql = sql;
this.column = column;
this.values = Arrays.asList(values);
this.hasValue = true;
}
public Where or() {
this.connect = OR;
return this;
}
public Where and() {
this.connect = AND;
return this;
}
/** * 獲取本次條件的鏈接符 * * @return */
public String getConnect() {
return connect;
}
protected String getSql() {
return sql;
}
protected boolean isHasValue() {
return hasValue;
}
protected List<Object> getValues() {
return values;
}
public String getColumn() {
return column;
}
}
複製代碼
上面中的常量 PLACEHOLDER 是做爲一個佔位符使用的,下面會說道。
這樣,一個用於保存單個條件的類就寫好了,在一個sql中有多個條件的話,只須要用一個ArrayList保存這些條件,並按照必定的條件拼裝成sql就行了。
sql中還有一些比較經常使用的判斷,好比:!= , = , <= , >= 等等,咱們在這裏能夠建立一個工具類來快速的生成Where 這個類,能夠這樣寫:
Wheres.java
import java.util.Arrays;
/** * 查詢條件 * @author hjx */
public class Wheres {
public static Where equal(final String columnName, final Object value) {
return new Where(columnName, Where.PLACEHOLDER + " = ? ", value);
}
public static Where notEqual(final String columnName, final Object value) {
return new Where(columnName, Where.PLACEHOLDER + " != ? ", value);
}
public static Where not(final String columnName, final Object value) {
return new Where(columnName, Where.PLACEHOLDER + " <> ? ", value);
}
public static Where isNotNull(final String columnName) {
return new Where(columnName, Where.PLACEHOLDER + " IS NOT NULL ");
}
public static Where isNull(final String columnName) {
return new Where(columnName, Where.PLACEHOLDER + " IS NULL ");
}
public static Where greater(final String columnName, final Object value, final boolean andEquals) {
if (andEquals) {
return new Where(columnName, Where.PLACEHOLDER + " >= ? ", value);
}
return new Where(columnName, Where.PLACEHOLDER + " > ? ", value);
}
public static Where less(final String columnName, final Object value, final boolean andEquals) {
if (andEquals) {
return new Where(columnName, Where.PLACEHOLDER + " <= ? ", value);
}
return new Where(columnName, Where.PLACEHOLDER + " < ? ", value);
}
public static Where like(final String columnName, final Object value) {
return new Where(columnName, Where.PLACEHOLDER + " like ? ", value);
}
public static Where betweenAnd(final String columnName, final Object value1st, final Object value2nd) {
return new Where(columnName, Where.PLACEHOLDER + " between ? and ? ", new Object[]{value1st, value2nd});
}
public static Where in(final String columnName, final Object[] values) {
Object[] sqlVal = values;
if (sqlVal.length == 0) {
sqlVal = new Object[]{null};
}
StringBuffer inSql = new StringBuffer();
inSql.append(Where.PLACEHOLDER);
inSql.append(" IN ( ");
String[] strings = StringUtils.repeat("?", sqlVal.length);
inSql.append(StringUtils.join(Arrays.asList(strings), ", "));
inSql.append(" ) ");
return new Where(columnName, inSql.toString(), sqlVal);
}
}
複製代碼
這裏只是簡單的列出了一些經常使用的判斷條件,若是有特殊須要的本身再加進去就行了。
關於常量 PLACEHOLDER 是這麼一回事:
在生成sql 的時候,我須要作一些字段上的驗證。這裏在sql中使用一個佔位符放進sql中,真正參與條件的字段放在另一個屬性中保存。這樣在真正生成sql的時候能夠驗證條件中的字段在不在表中,若是存在的話將字段和佔位符進行替換就行了。而且若是使用的是屬性名稱的話,也能夠根據名稱找到對應的表的字段名。
經過上面的代碼,咱們能夠很方便的建立條件了。如今咱們將這些條件組裝成咱們須要的完整的sql。
注意:這裏的代碼可能和個人github上的不太同樣,由於這裏只講一下思路,具體的怎麼將全部的代碼組裝起來讓它成爲一個完整的項目,每一個人都不同。因此~~~ 嘿嘿。
如今開始:
咱們仍是以以前寫的User.java爲例子
List<Where> wheres = Arrays.asList(
Wheres.equal("name", "李叔叔"),
Wheres.notEqual("status", 1),
Wheres.in("age", new Integer[]{1, 2, 3, 4, 5}),
Wheres.greater("age", 20, true)
);
List<Object> sqlValue = new ArrayList<>();
StringBuilder sql = new StringBuilder();
if (wheres.size() != 0) {
sql.append("WHERE ");
for (int i = 0; i < wheres.size(); i++) {
Where where = wheres.get(i);
if (i != 0) {
sql.append(where.getConnect());
}
String column = where.getColumn();
String whereSql = where.getSql();
sql.append(
//這裏獲取真實sql
whereSql.replace(Where.PLACEHOLDER, getColumnName(column))
);
//由於有些條件中的參數多是有多個
List<Object> values = where.getValues();
for (int j = 0; j < values.size(); j++) {
sqlValue.add(values.get(j));
}
}
}
System.out.println(sql.toString());
System.out.println(sqlValue.toString());
複製代碼
這裏說明一下:getColumnName(String name) ,這個方法是根據參數獲取真正的字段名稱的方法。由於這個條件中可能傳入的是java屬性的名稱而不是表的字段名稱,須要轉換成爲真正的表的字段名。這一步也是從以前生成的映射中獲取的。順便還能驗證一下表中有沒有這個字段。這個方法我就不貼出來了,github上有。
輸出結果:
WHERE name = ? AND status != ? AND age IN ( ?, ?, ?, ?, ? ) AND age >= ?
[李叔叔, 1, 1, 2, 3, 4, 5, 20]
複製代碼
這裏一個where就寫好了,而且也能夠拿到條件中的參數了。
剩下的就是後面的單獨生成update,delete,select 類型sql的操做了。
上一篇講了怎樣生成一個sql中where的一部分,以後咱們要作事情就簡單不少了,就只要像最開始同樣的生成各類sql語句就行了,以後只要再加上咱們須要的條件,一個完整的sql就順利的作好了。
如今咱們開始寫生成查詢語句的sql。一個查詢語句大體上是這樣的:
SELECT name, id, create_date, age, mark, status FROM user
複製代碼
這裏能夠看出來,一個基礎的查詢語句基本上就是一個 SELECT 後面加上須要查詢的字段,跟上 FROM 和要查詢的表名稱就行了。 最多後面可能須要加上 ORDER BY/GROUP BY/LIMIT ....之類的就行了,由於比較簡單,這裏就不寫了。(太複雜的就直接寫sql就行了,我本身不須要這種操做)
這幾步都仍是比較好作的,第一步很簡單,仿照着以前寫的就能夠了。由於這裏在執行sql的時候,我使用的是JdbcTemplate,這裏有一個不大不小的坑,下面我說一下。
這個坑是我在使用我寫好的這個項目給公司作報表的時候碰到的。緣由是這樣,由於數據庫中有些字段是datetime類型的,這個字段有時候在表中的值是:0000-00-00 00:00:00,(我也不知道這個值是怎麼進去的,可是就是存在/(ㄒoㄒ)/~~)可是這個值是沒法轉換成爲java中的Date類型。因此這裏會報錯。
我在這裏寫了一個繼承SpringJdbc中的ColumnMapRowMapper的類,是這樣的:
import org.springframework.jdbc.core.ColumnMapRowMapper;
import java.sql.ResultSet;
import java.sql.SQLException;
/** * 捕獲取值的錯誤 * * @author hjx */
public class PlusColumnMapRowMapper extends ColumnMapRowMapper {
/** * 數據庫類型爲時間時, 若是值爲 0000-00-00 00:00:00 * 會報錯,因此重寫此方法,返回null * * @param rs * @param index * @return * @throws SQLException */
@Override
protected Object getColumnValue(ResultSet rs, int index) throws SQLException {
Object columnValue = null;
try {
columnValue = super.getColumnValue(rs, index);
} catch (SQLException e) {
e.printStackTrace();
}
return columnValue;
}
}
複製代碼
這個類具體在哪裏使用,會在下面說明。
如今說一下怎麼實現上面的思路,首先由於第一步比較簡單,就不寫了。我直接從第二步開始。
執行sql,並取出結果。
這裏我用的是JdbcTemplate的方法,這給咱們提供了一個方法:
<T> List<T> query(String sql, Object[] args, RowMapper<T> rowMapper) 複製代碼
這裏前兩個參數比較好理解,一個是sql,一個是sql中的參數。第三個是須要傳一個接口RowMapper,這個接口具體是幹啥的上網一查就知道了~~~
這裏面有一個方法:
T mapRow(ResultSet rs, int rowNum) throws SQLException 複製代碼
第一個參數是查詢的結果,第二個是指如今在第幾行結果,返回值是你要返回什麼對象。這裏咱們須要重寫這個方法,把查詢出的結果轉換成爲咱們須要的對象。咱們能夠這麼寫:
/** * 把數據庫查詢的結果與對象進行轉換 * * @param resultSet * @param rowNum * @return * @throws SQLException */
@Override
public T mapRow(ResultSet resultSet, int rowNum) throws SQLException {
Map<String, Object> resultMap = columnMapRowMapper.mapRow(resultSet, rowNum);
。。。。
複製代碼
這個方法中的columnMapRowMapper 就是上面咱們寫的PlusColumnMapRowMapper,它的做用就是將查詢結果第 rowNum 拿出來,而且將結果轉換過成爲一個 Map<String, Object>。其中:
key :是表字段名稱。
Object :該字段的值。
上面寫的PlusColumnMapRowMapper主要做用就是在獲取值的時候若是發生異常,返回一個null。
在這一步裏咱們已經拿到了執行sql的結果,如今咱們要將結果轉換過爲咱們須要的class。
將結果轉換爲class
在上一步咱們拿到了存放結果Map,如今只須要將map遍歷一下,而後實例化java對象,根據字段和屬性的映射關係使用反射將屬性一個個的set進去就行了。如今貼上上一步的完整代碼:
public T mapRow(ResultSet resultSet, int rowNum) throws SQLException {
Map<String, Object> resultMap = columnMapRowMapper.mapRow(resultSet, rowNum);
T instance = getInstance(tableClass);
for (Map.Entry<String, Object> entry : resultMap.entrySet()) {
//數據庫字段名
String key = entry.getKey();
if (!columnFieldMapper.containsKey(key)) {
continue;
}
Field declaredField = columnFieldMapper.get(key);
if (declaredField == null) {
continue;
}
//數據庫字段值
Object value = entry.getValue();
setFieldValue(instance, declaredField, value);
}
return instance;
}
複製代碼
其中 columnFieldMapper 是一個Map<String, Field>。key是表的字段個名稱。value是對應的class的屬性。
下面是 setFieldValue的具體代碼:
boolean setFieldValue(T t, Field field, Object value) {
field.setAccessible(true);
try {
if (value != null) {
field.set(t, value);
return true;
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return false;
}
複製代碼
這樣,就能夠將查詢出的結果根據映射關係轉換成爲咱們須要的class了。
若是查詢須要添加條件的話,可使用以前講的 生成條件的工具將條件的sql拼接在這裏的sql後面,相應的,where裏的參數也要按照順序添加進數組就行了。
相同的,若是要添加 ORDER BY/GROUP BY/LIMIT這些東西的話也是同樣的操做。主要仍是要看本身的代碼是怎麼設計的了。我本身用的只寫了ORDER BY 和 LIMIT 。能夠在個人github上找到。地址在這裏:github.com/hjx60149632… 。
誒呀, 這個太簡單了。不寫了哦~~~
參照我以前寫的,分析一下,想想思路,而後每一步要怎麼作,一點一點的就寫好了。
~~~
你本身寫咯~~~。
最後一部分了,立刻就寫完了。寫東西真的好累啊~~~
更新的語句也比較好作,sql後面的條件由於在以前已經寫了where這一篇,因此這裏就只寫sql中where左邊的一部分。如今仍是先分析一下 **update **語句:
UPDATE user SET name = ? , id = ? , create_date = ? , age = ? , status = ? WHERE id = ?
複製代碼
能夠看到的,大致上就是 UPDATE 表名稱 SET 字段名稱 = ? 這個樣子的。(由於如今不寫WHERE右邊的)
因此具體的思路就是:
根據映射關係拼裝sql。
這裏可能有一個能夠選擇的地方,就是若是某一個屬性的值是null,這時要不要把這個屬性更新爲null。
拿到要更新的值。
執行sql。
從映射中拿到全部的屬性。
這一步的代碼就不放了~~~,和前面寫的沒有什麼區別。
拿到要更新的屬性名稱,和值。
這裏咱們須要三個參數:
1:用來標示更新的時候是否須要忽略值是null的屬性。 boolean ignoreNull
2:用來保存須要更新的字段的有序集合。 List updataColumn
3:保存須要更新的字段的值的有序集合。 List values
代碼是這樣的:
List<String> columnNames = new ArrayList<>(entityTableRowMapper.getColumnNames());
Map<String, Field> columnFieldMapper = entityTableRowMapper.getColumnFieldMapper();
List<Object> values = new ArrayList<>();
for (int i = 0; i < columnNames.size(); i++) {
String columnName = columnNames.get(i);
if (!sqlColumns.contains(columnName)) {
continue;
}
Field field = columnFieldMapper.get(columnName);
Object value = EntityUtils.getValue(entity, field);
//若是class中的值是null,而且設置忽略null,跳過
if (ignoreNull && value == null) {
continue;
}
updataColumn.add(columnName);
values.add(value);
}
複製代碼
根據拿到的數據拼裝sql
拿到上面須要的數據後,咱們還須要拿到表的名稱,這一步直接從映射關係中取就行了。下面的是拼裝sql的代碼:
StringBuilder sql = new StringBuilder();
sql.append("UPDATE ").append(getTableName()).append(StringUtils.SPACE);
sql.append("SET ");
for (int i = 0; i < updataColumn.size(); i++) {
String column = updataColumn.get(i);
if (i == 0) {
sql.append(StringUtils.append(column, " = ? "));
} else {
sql.append(StringUtils.append(", ", column, " = ? "));
}
}
複製代碼
這樣就行了,大體上是這樣的:
UPDATE user SET name = ? , id = ? , create_date = ? , age = ? , status = ?
複製代碼
條件的話,用以前寫的where生成就行了,where中的值加在集合values的後面就行了。
執行sql。
太簡單了,就不寫了~
終於寫完了。
仍是說一下,由於代碼已經在github上了,因此沒有把所有的代碼寫在上面,主要仍是以說明思路爲主。另外剛開始寫博客,有些可能表達的不是很明白。吃了沒文化的虧啊~~~
這個項目還有不少能夠可是尚未實現的功能,好比一些比較複雜的查詢,執行函數之類的。我並沒去寫它。一是不須要,由於這個東西平時主要是作導出報表的時候用的,二是我本身寫項目的話壓根就不會用到這些東西,能用java寫的我都用java寫了。數據庫嘛,對我來講就存個數據就行了,數據處理上的事情仍是交給java來作好一點。