1. 程式人生 > >存在即合理,重複輪子orm java版本

存在即合理,重複輪子orm java版本

1,業務描述前序?

需求來源於,公司的運營部門。本人所在公司(私營,遊戲行業公司),從初創業,我就進入公司,一直致力於伺服器核心研發。

公司成立塊3年了,前後出產了4款遊戲,一直在重複的製造公司遊戲對應的遊戲後臺管理工具(web版本);

今年年初公司成立裡運營部門,不僅開始運營公司自己產品也代理了其他公司產品。越來越覺得做出統一的平臺管理和遊戲後臺管理工具迫切需求;

既然有需求,就有市場,就需要研發;

2,重複造輪子

既然是統一的管理平臺,問題來了,可能做過遊戲或者遊戲後臺相關的人員都知道,管理平臺,其實需求簡單明瞭,業務簡單清晰!

問題是在於資料量很大,針對一個遊戲,一個玩家,一分鐘就可能產出上百條日誌記錄(升級,獲得任何獎勵,消耗任何道具等等);

然而在面對一個遊戲,多個伺服器,多個遊戲,很多個伺服器情況下,日誌量很難預估;且還有一個特色,那就是一般情況下一個月以前的日誌其實是完全無用,並且可以刪除的;

3,DBA的重要性

我們公司到目前為止沒有專業的DBA和DBC,資料庫相關的設計和操作只能自己來;

(我的40米大刀呢?)

沒辦法只能自己上

寶寶心裡苦啊

好了廢話不多說,

由此上面的綜合原因,我們考慮了,日誌,不同的業務邏輯日誌不同的實體模型(資料庫表結構),

但是每一天劃分開來(比如Test 表今天的Test_2016_11_04,明天:Test_2016_11_05);

這樣的業務邏輯下我沒有發現比較適合自己的orm框架,或者說我英文不好,看不懂一些api吧,

於是就有了重複造輪子的事情;

3,我的業務量在哪裡?

我的業務量主要還是在寫入資料,作為日誌伺服器(http api 提供方式)只提供資料接收,驗證,存取操作;

也就說我只需要考慮大併發情況下,批量寫入問題,並且防止好sql注入式攻擊就好;

4,程式碼實現

   1 package net.sz.engine.db;
   2 
   3 import java.io.ByteArrayInputStream;
   4 import java.io.ByteArrayOutputStream;
   5 import java.io.IOException;
   6 import java.io.ObjectInputStream;
7 import java.io.ObjectOutputStream; 8 import java.lang.reflect.Field; 9 import java.lang.reflect.InvocationTargetException; 10 import java.lang.reflect.Method; 11 import java.lang.reflect.Modifier; 12 import java.sql.Connection; 13 import java.sql.Date; 14 import java.sql.PreparedStatement; 15 import java.sql.ResultSet; 16 import java.sql.SQLException; 17 import java.util.ArrayList; 18 import java.util.HashMap; 19 import java.util.List; 20 import java.util.Map; 21 import java.util.concurrent.ConcurrentHashMap; 22 import javax.persistence.Column; 23 import javax.persistence.GeneratedValue; 24 import javax.persistence.Id; 25 import javax.persistence.Table; 26 import net.sz.engine.utils.StringUtil; 27 import org.apache.log4j.Logger; 28 29 /** 30 * 31 * <br> 32 * author 失足程式設計師<br> 33 * mail [email protected]<br> 34 * phone 13882122019<br> 35 */ 36 public abstract class Dao { 37 38 private static final Logger log = Logger.getLogger(Dao.class); 39 /** 40 * 資料庫連線 41 */ 42 protected String dbUrl; 43 /** 44 * 資料庫名字 45 */ 46 protected String dbName; 47 /** 48 * 資料庫使用者 49 */ 50 protected String dbUser; 51 /** 52 * 資料庫密碼 53 */ 54 protected String dbPwd; 55 /** 56 * 是否顯示sql語句 57 */ 58 protected boolean showSql; 59 /** 60 * 儲存所有型別解析 61 */ 62 protected static final ConcurrentHashMap<String, List<SqlColumn>> sqlColumnMap = new ConcurrentHashMap<>(); 63 64 public Dao() { 65 } 66 67 //<editor-fold defaultstate="collapsed" desc="建構函式 public Dao(String dbUrl, String dbName, String dbUser, String dbPwd, boolean showSql)"> 68 /** 69 * 建構函式 70 * 71 * @param dbUrl 72 * @param dbName 73 * @param dbUser 74 * @param dbPwd 75 * @param showSql 76 */ 77 public Dao(String dbUrl, String dbName, String dbUser, String dbPwd, boolean showSql) { 78 this.dbUrl = dbUrl; 79 this.dbName = dbName; 80 this.dbUser = dbUser; 81 this.dbPwd = dbPwd; 82 this.showSql = showSql; 83 } 84 //</editor-fold> 85 86 //<editor-fold defaultstate="collapsed" desc="獲取表名 protected String getTableName(Object o)"> 87 /** 88 * 獲取表名 89 * 90 * @param oClass 91 * @return 92 */ 93 protected String getTableName(Class<?> oClass) { 94 //判斷指定型別的註釋是否存在於此元素上 95 boolean isHaveTable = oClass.isAnnotationPresent(Table.class); 96 if (!isHaveTable) { 97 return oClass.getSimpleName();//不存在就不需要獲取其表名 98 } 99 Table table = oClass.getAnnotation(Table.class);//拿到對應的表格註解型別 100 return table.name();//返回註解中的值,也就是表名 101 } 102 //</editor-fold> 103 104 //<editor-fold defaultstate="collapsed" desc="設定欄位值,插入資料庫,支援sql注入攻擊 protected void setStmtParams(PreparedStatement stmt, SqlColumn sqlColumn, Integer nums, Object value)"> 105 /** 106 * 設定欄位值,插入資料庫,支援sql注入攻擊 107 * 108 * @param stmt 109 * @param sqlColumn 110 * @param nums 111 * @param value 112 * @throws SQLException 113 * @throws IOException 114 */ 115 protected void setStmtParams(PreparedStatement stmt, SqlColumn sqlColumn, Integer nums, Object value) throws SQLException, IOException { 116 switch (sqlColumn.getClassType().getName().toLowerCase()) { 117 case "int": 118 case "java.lang.integer": 119 if (value == null) { 120 if (!sqlColumn.isColumnNullAble()) { 121 value = 0; 122 } 123 } 124 if (value == null) { 125 stmt.setObject(nums, null); 126 } else { 127 stmt.setInt(nums, (Integer) value); 128 } 129 130 break; 131 case "string": 132 case "java.lang.string": 133 if (value == null) { 134 if (!sqlColumn.isColumnNullAble()) { 135 value = ""; 136 } 137 } 138 stmt.setString(nums, (String) value); 139 break; 140 case "double": 141 case "java.lang.double": 142 if (value == null) { 143 if (!sqlColumn.isColumnNullAble()) { 144 value = 0.0; 145 } 146 } 147 if (value == null) { 148 stmt.setObject(nums, null); 149 } else { 150 stmt.setDouble(nums, (Double) value); 151 } 152 break; 153 case "float": 154 case "java.lang.float": 155 if (value == null) { 156 if (!sqlColumn.isColumnNullAble()) { 157 value = 0.0; 158 } 159 } 160 if (value == null) { 161 stmt.setObject(nums, null); 162 } else { 163 stmt.setFloat(nums, (float) value); 164 } 165 break; 166 case "long": 167 case "java.lang.long": 168 if (value == null) { 169 if (!sqlColumn.isColumnNullAble()) { 170 value = 0.0; 171 } 172 } 173 if (value == null) { 174 stmt.setObject(nums, null); 175 } else { 176 stmt.setLong(nums, (long) value); 177 } 178 break; 179 case "byte": 180 case "java.lang.byte": 181 if (value == null) { 182 if (!sqlColumn.isColumnNullAble()) { 183 value = 0.0; 184 } 185 } 186 if (value == null) { 187 stmt.setObject(nums, null); 188 } else { 189 stmt.setByte(nums, (byte) value); 190 } 191 break; 192 case "short": 193 case "java.lang.short": 194 if (value == null) { 195 if (!sqlColumn.isColumnNullAble()) { 196 value = 0.0; 197 } 198 } 199 if (value == null) { 200 stmt.setObject(nums, null); 201 } else { 202 stmt.setShort(nums, (short) value); 203 } 204 break; 205 case "date": 206 case "java.lang.date": 207 if (value == null) { 208 if (!sqlColumn.isColumnNullAble()) { 209 value = 0.0; 210 } 211 } 212 stmt.setDate(nums, (Date) value); 213 break; 214 default: { 215 if (value == null) { 216 stmt.setObject(nums, null); 217 } else { 218 stmt.setBytes(nums, writeObject(value)); 219 } 220 } 221 } 222 } 223 //</editor-fold> 224 225 //<editor-fold defaultstate="collapsed" desc="設定欄位值,插入資料庫,支援sql注入攻擊 protected void setStmtParams(PreparedStatement stmt, SqlColumn sqlColumn, Integer nums, Object value)"> 226 /** 227 * 設定欄位值,插入資料庫,支援sql注入攻擊 228 * 229 * @param stmt 230 * @param sqlColumn 231 * @param nums 232 * @param value 233 * @throws SQLException 234 * @throws IOException 235 */ 236 protected void setStmtParams(PreparedStatement stmt, Integer nums, Object value) throws SQLException, IOException { 237 if (value == null) { 238 stmt.setObject(nums, null); 239 return; 240 } 241 switch (value.getClass().getName().toLowerCase()) { 242 case "int": 243 case "java.lang.integer": 244 stmt.setInt(nums, (Integer) value); 245 break; 246 case "string": 247 case "java.lang.string": 248 stmt.setString(nums, (String) value); 249 break; 250 case "double": 251 case "java.lang.double": 252 stmt.setDouble(nums, (Double) value); 253 break; 254 case "float": 255 case "java.lang.float": 256 stmt.setFloat(nums, (float) value); 257 break; 258 case "long": 259 case "java.lang.long": 260 stmt.setLong(nums, (long) value); 261 break; 262 case "byte": 263 case "java.lang.byte": 264 stmt.setByte(nums, (byte) value); 265 break; 266 case "short": 267 case "java.lang.short": 268 stmt.setShort(nums, (short) value); 269 break; 270 case "date": 271 case "java.lang.date": 272 stmt.setDate(nums, (Date) value); 273 break; 274 default: { 275 stmt.setBytes(nums, writeObject(value)); 276 } 277 } 278 } 279 //</editor-fold> 280 281 //<editor-fold defaultstate="collapsed" desc="反射獲取欄位資訊 過濾 transient 欄位 protected Map<String, SqlColumn> getColumns(Object o)"> 282 /** 283 * 反射獲取欄位資訊 過濾 transient 欄位 284 * 285 * @param clazz 286 * @return 287 */ 288 protected List<SqlColumn> getColumns(Class<?> clazz) { 289 List<SqlColumn> cols = sqlColumnMap.get(clazz.getName()); 290 if (cols != null) { 291 return cols; 292 } 293 //獲取物件中所有的屬性 294 Field[] fields = clazz.getDeclaredFields(); 295 Method[] methods = clazz.getMethods(); 296 cols = new ArrayList<>(); 297 boolean ispakey = false; 298 //遍歷所有屬性 299 for (Field field : fields) { 300 //忽略欄位,靜態欄位,最終欄位,不會書寫到資料庫 301 if (Modifier.isTransient(field.getModifiers()) 302 || Modifier.isStatic(field.getModifiers()) 303 || Modifier.isFinal(field.getModifiers())) { 304 if (showSql) { 305 log.error("類:" + clazz.getName() + " 欄位:" + field.getName() + " is transient or static or final;"); 306 } 307 continue; 308 } 309 310 //如果屬性上有對應的列註解型別則獲取這個註解型別 311 Column column = field.getAnnotation(Column.class); 312 SqlColumn sqlColumn = new SqlColumn(); 313 sqlColumn.setColumnName(field.getName()); 314 sqlColumn.setFieldName(field.getName()); 315 316 if (column != null) { 317 if (column.name() != null && !column.name().trim().isEmpty()) { 318 sqlColumn.setColumnName(column.name().trim()); 319 } 320 321 if (column.length() > 0) { 322 sqlColumn.setColunmLength(column.length()); 323 } 324 325 sqlColumn.setColumnNullAble(column.nullable()); 326 327 if (column.columnDefinition() != null) { 328 sqlColumn.setColumnDefinition(column.columnDefinition()); 329 } 330 } 331 //拿到對應屬性的型別,然後根據對應的型別去宣告欄位型別 332 Class<?> type = field.getType(); 333 334 sqlColumn.setClassType(type); 335 336 String columnvalue = null; 337 switch (type.getName().toLowerCase()) { 338 case "int": 339 case "java.lang.integer": 340 columnvalue = "int(4)"; 341 break; 342 case "string": 343 case "java.lang.string": 344 if (sqlColumn.getColunmLength() < 1000) { 345 columnvalue = "varchar(" + sqlColumn.getColunmLength() + ")"; 346 } else { 347 columnvalue = "text(" + sqlColumn.getColunmLength() + ")"; 348 } 349 break; 350 case "double": 351 case "java.lang.double": 352 columnvalue = "double"; 353 break; 354 case "float": 355 case "java.lang.float": 356 columnvalue = "float"; 357 break; 358 case "byte": 359 case "java.lang.byte": 360 columnvalue = "tinyint(1)"; 361 break; 362 case "long": 363 case "java.lang.long": 364 columnvalue = "bigint"; 365 break; 366 case "short": 367 case "java.lang.short": 368 columnvalue = "tinyint(2)"; 369 break; 370 default: 371 372 columnvalue = "blob"; 373 374 break; 375 } 376 if (columnvalue != null) { 377 //如果屬性上有對應的主鍵ID註解型別則獲取這個註解型別 378 Id tpid = field.getAnnotation(Id.class); 379 if (tpid != null) { 380 ispakey = true; 381 sqlColumn.setColumnkey(true); 382 sqlColumn.setColumnNullAble(false); 383 GeneratedValue annotation = field.getAnnotation(GeneratedValue.class); 384 //判斷主鍵是否為自動增長 385 if (annotation != null) { 386 sqlColumn.setColumnAuto(true); 387 } 388 } 389 390 if (sqlColumn.isColumnNullAble()) { 391 columnvalue += " null"; 392 } else { 393 columnvalue += " not null"; 394 } 395 396 if (sqlColumn.isColumnkey()) { 397 if (sqlColumn.isColumnAuto()) { 398 columnvalue += " auto_increment"; 399 } 400 columnvalue += " primary key"; 401 } 402 403 sqlColumn.setValue(columnvalue); 404 405 for (Method method : methods) { 406 String methodName = method.getName().toLowerCase();//獲取每一個方法名 407 if (methodName.equals("get" + sqlColumn.getFieldName().toLowerCase())) { 408 sqlColumn.setGetMethod(method); 409 break; 410 } 411 } 412 413 for (Method method : methods) { 414 String methodName = method.getName().toLowerCase();//獲取每一個方法名 415 if (methodName.equals("set" + sqlColumn.getFieldName().toLowerCase())) { 416 sqlColumn.setSetMethod(method); 417 break; 418 } 419 } 420 421 cols.add(sqlColumn); 422 } else { 423 if (showSql) { 424 log.error("類:" + clazz.getName() + " 無法識別的欄位:" + field.getName() + " ;"); 425 } 426 } 427 } 428 if (!ispakey) { 429 throw new UnsupportedOperationException("實體類不允許沒有元件欄位:" + clazz.getName()); 430 } 431 if (cols.isEmpty()) { 432 throw new UnsupportedOperationException("實體模型未有任何欄位:" + clazz.getName()); 433 } 434 sqlColumnMap.put(clazz.getName(), cols); 435 return cols; 436 } 437 //</editor-fold> 438 439 //<editor-fold defaultstate="collapsed" desc="序列化一個物件 protected byte[] writeObject(Object obj) "> 440 /** 441 * 序列化一個物件 442 * 443 * @param obj 要序列化的物件 444 * @return byte陣列 445 * @throws java.io.IOException 446 */ 447 protected byte[] writeObject(Object obj) throws IOException { 448 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 449 ObjectOutputStream out = null; 450 try { 451 out = new ObjectOutputStream(baos); 452 out.writeObject(obj); 453 } finally { 454 try { 455 out.close(); 456 } catch (IOException e) { 457 } 458 } 459 return baos.toByteArray(); 460 }