一、傳統的JDBC編程
在java開發中,以前都是通過JDBC(Java Data Base Connectivity)與數據庫打交道的,至少在ORM(Object Relational Mapping)框架沒出現之前是這樣,目前常用的ORM框架有JPA、hibernate、mybatis、spring jdbc等,我一開始也是使用JDBC編程,后面開始使用hibernate,有一次開發一個CRM管理系統使用的是Spring JDBC操作數據庫,但個人還是不太喜歡這個框架,本人目前使用的最多還是通過mybatis操作數據庫,盡管我現在使用的是Spring boot開發,繼承了JPA來操作數據庫,但是實體類和dao、service、controller層的基本CRUD操作還是通過hibernate代碼工具自動生成的,其他操作都是通過mybatis自己編寫SQL語句來操作的,因此本文只提JDBC到hibernate再到mybatis的一個過程。
1、什么是JDBC
JDBC(Java Data Base Connectivity,java數據庫連接)是一種用於執行SQL語句的Java API,是用Java語言編寫的類和接口組成的,可以為多種關系型數據庫提供統一訪問的接口。JDBC提供了一種基准,說白了,也就是sun公司為各大數據庫廠商的關系型數據庫連接java所制定的規范,因此他們只需要實現JDBC的接口規范即可,而具體的實現是由各大數據庫廠商去實現的,由於每種數據庫的獨特性,Java是無法控制的,所以JDBC是一種典型的橋接模式。我們據此也可以構建更高級的工具和接口,比如封裝常用的CRUD工具類,使數據庫開發人員能夠更快速、高效、簡便的開發數據庫應用程序。
2、使用JDBC進行CRUD操作
/**
*
* @description: JDBC連接MySQL數據庫進行CRUD操作
*
* 步驟:
* 1、加載驅動和注冊數據庫信息。
* 2、打開Connection,獲取PreparedStatement對象。
* 3、通過PreparedStatement執行SQL,返回結果到ResultSet對象。
* 4、使用ResultSet讀取數據,然后通過代碼轉換為具體的POJO對象。
* 5、關閉數據庫相關資源,先開的后關,后開的先關。
*
* @author: liuhongwei
*/
public class JdbcTest {
private Logger logger = LoggerFactory.getLogger(getClass());
private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
private static final String URL = "jdbc://mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8";
private static final String USERNAME = "root";
private static final String PASSWORD = "root";
private Connection getConnection() {
Connection conn = null;
try {
// 加載驅動和注冊數據庫信息
Class.forName(JDBC_DRIVER);
DriverManager.getConnection(URL, USERNAME, PASSWORD);
} catch (ClassNotFoundException | SQLException e) {
logger.info("Class={JdbcTest.class.getName()} not found", JdbcTest.class.getName(), e);
}
return conn;
}
/**
*
* @description: 保存用戶信息
* @param user
* @return
*/
public int save(User user) {
Connection conn = getConnection();
int row = 0;
// 5個問號(占位符)代表5個字段預先要保留的值
String sql = "insert into tb_user (username,password,name,sex,email,tel) values(?,?,?,?,?,?)";
PreparedStatement ps = null;
try {
/**
* 使用PreparedStatement的優點:
* 1、具有預編譯功能,相同的SQL語句只需要編譯一次,提高執行效率。
* 2、可以防止SQL語句注入,提高安全性
*/
// 使用PreparedStatement對象里來構建並執行SQL語句
ps = conn.prepareStatement(sql);
// 通過PreparedStatement對象里的set方法設置要插入的值
ps.setString(1, user.getUsername());
ps.setString(2, user.getPassword());
ps.setString(3, user.getName());
ps.setInt(4, user.getSex());
ps.setString(5, user.getEmail());
ps.setString(6, user.getTel());
// 返回影響行數
row = ps.executeUpdate();
} catch (SQLException e) {
logger.info("Bad SQL Grammer", e);
} finally {
close(null, ps, conn);
}
return row;
}
/**
*
* @description: 修改用戶信息
* @param user
* @return
*/
public int update(User user) {
Connection conn = getConnection();
int row = 0;
String sql = "update tb_user set password=? where username=?";
PreparedStatement ps = null;
try {
ps = conn.prepareStatement(sql);
ps.setString(1, user.getPassword());
ps.setString(2, user.getUsername());
row = ps.executeUpdate();
} catch (SQLException e) {
logger.info("Bad SQL Grammer", e);
} finally {
close(null, ps, conn);
}
return row;
}
/**
*
* @description: 根據id刪除用戶
* @param id
* @return
*/
public int delete(Long id) {
Connection conn = getConnection();
int row = 0;
String sql = "delete from tb_user where id='" + id + "'";
PreparedStatement ps = null;
try {
ps = conn.prepareStatement(sql);
row = ps.executeUpdate();
} catch (SQLException e) {
logger.info("Bad SQL Grammer", e);
} finally {
close(null, ps, conn);
}
return row;
}
/**
*
* @description: 根據id查詢用戶信息
* @param id
* @return
*/
public User getAll(Long id) {
Connection conn = getConnection();
String sql = "select id,username,password,name,sex,email,tel from tb_user where id = ?";
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(sql);
ps.setLong(1, id);
// 通過PreparedStatement執行Sql,返回結果到ResultSet對象
rs = ps.executeQuery();
// 遍歷結果集
while (rs.next()) {
Long userId = rs.getLong("id");
String username = rs.getString("username");
String password = rs.getString("password");
String name = rs.getString("name");
Integer sex = rs.getInt("sex");
String email = rs.getString("email");
String tel = rs.getString("tel");
User user = new User();
user.setId(userId);
user.setUsername(username);
user.setPassword(password);
user.setName(name);
user.setSex(sex);
user.setEmail(email);
user.setTel(tel);
return user;
}
} catch (SQLException e) {
logger.info("Bad SQL Grammer", e);
}
return null;
}
/**
*
* @description: 釋放資源,注意:先開的后關,后開的先關
* @param rs
* @param ps
* @param conn
*/
public void close(ResultSet rs, PreparedStatement ps, Connection conn) {
try {
if (rs != null && rs.isClosed()) {
rs.close();
}
} catch (SQLException e1) {
e1.printStackTrace();
}
try {
if (ps != null && ps.isClosed()) {
ps.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (conn != null && conn.isClosed()) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
從這個簡易的CRUD例子中我們可以看到使用傳統JDBC操作數據庫存在的一些弊端:
①開發工作量較大。我們需要先連接,然后處理JDBC底層事務,處理數據類型,還需要操作Connection對象、Statement/PreparedStatement對象、ResultSet對象去拿數據,並且還要關閉這些資源,難以寫出高質量、已維護的代碼。
②使用JDBC操作數據庫,使用時才連接,不使用就釋放,這種頻繁的對數據庫操作,將導致資源浪費、影響數據庫的性能。
③不管是程序中的SQL語句、向PreparedStatement對象設置參數還是從ResultSet遍歷結果集數據時,都是采用硬編碼的方式,因此不利於后期系統的維護。
在一個如此簡單的CRUD例子都是如此,何況更為復雜的操作呢?於是ORM框架便出現了,但是歸根結底,ORM框架還是基於JDBC編程,只不過是對JDBC的封裝形式、強度和方式不同罷了。
二、采用ORM框架操作數據庫
1、什么是ORM
對象關系映射(Object Relational Mapping,簡稱ORM,或O/RM,或O/R mapping),類對應數據庫的表,屬性對應表中的列,對象對應表中的每一條數據,是為了解決面向對象與面向關系型數據庫存在的互不匹配的現象的技術,主要是通過一個配置文件進行關聯的。它使我們編程的思想更面向對象了,不用再去考慮關系型數據庫。
2、Spring集成hibernate操作數據庫
我們知道SUN公司一開始推出JavaEE服務器端組件模型(EJB),但是由於EJB配置過於復雜,加上其自身笨重,且適用范圍小,很快就被淘汰了。於是就有了hibernate框架,而hibernate從誕生起就成了ORM框架的花旦,直至今日。同時我應該感謝的是,正因為SUN公司推出EJB模型,也才有了Spring的出現,讓Java編程世界璀璨奪目。
hibernate是建立在若干POJO通過XML映射文件或注解提供的規則映射到數據庫表中的,也就是說,我們可以通過POJO直接操作數據庫的數據,它提供的是一種全表映射的模型。
下面以開發用戶類為例進行講解,首先我們需要提供user.hbm.xml映射文件,制定映射規則,如下代碼所示。
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.lhw.test.user.dao.entity.User" table="tb_user">
<id name="id" type="java.lang.Long">
<column name="id" />
<generator class="native" />
</id>
<property name="username" type="java.lang.String">
<column name="username" length="50">
<comment>用戶名</comment>
</column>
</property>
<property name="password" type="java.lang.String">
<column name="password" length="20">
<comment>密碼</comment>
</column>
</property>
<property name="name" type="java.lang.String">
<column name="name" length="50">
<comment>姓名</comment>
</column>
</property>
<property name="sex" type="java.lang.Integer">
<column name="sex" length="1">
<comment>性別</comment>
</column>
</property>
<property name="email" type="java.lang.String">
<column name="email" length="50">
<comment>郵箱</comment>
</column>
</property>
<property name="tel" type="java.lang.String">
<column name="tel" length="20">
<comment>電話</comment>
</column>
</property>
</class>
</hibernate-mapping>
這是一個非常簡單的映射配置文件,主要就是描述POJO與數據庫中表的映射關系。通過hibernate操作數據庫幾乎可以不用編寫SQL語句即可操作數據庫中的數據,如果對於SQL語句並不擅長的人來說,可謂是天助也。
那么,POJO與數據庫中表的映射配置文件有了,但是還需要一個配置文件,它就是將面向對象與面向關系型數據庫聯系起來的配置文件hibernate.cfg.xml,它是全局配置文件。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- hibernate配置文件,與數據庫類型無關,在applicationContext.xml中被加載 -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<description>sessionFactory配置</description>
<property name="dataSource">
<ref bean="dataSource"/>
</property>
<property name="useTransactionAwareDataSource" value="true"></property>
<property name="mappingDirectoryLocations">
<!-- hibernate映射文件List -->
<list>
<value>classpath:/com/lhw/test/user/dao/hibernate</value>
</list>
</property>
<!-- 配置hibernate屬性 -->
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
<prop key="hibernate.jdbc.fetch_size">100</prop>
<prop key="hibernate.jdbc.batch_size">80</prop>
<prop key="hibernate.connection.release_mode">on_close</prop>
<prop key="hibernate.jdbc.use_scrollable_resultset">true</prop>
<prop key="hibernate.cglib.use_reflection_optimizer">false</prop>
<prop key="hibernate.generate_statistics">false</prop>
<prop key="hibernate.query.factory_class">org.hibernate.hql.ast.ASTQueryTranslatorFactory</prop>
<!-- sqldebug -->
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.use_sql_comments">true</prop>
<prop key="hibernate.format_sql">true</prop>
</props>
</property>
</bean>
</beans>
接下來我們就要利用hibernate對數據庫中數據進行操作了,那么首先我們需要創建sessionFactory,然后打開session,再去操作數據庫,下面代碼是封裝hibernate原生API的DAO泛型基類,可直接進行CRUD操作,也可擴展使用,當然這是在Spring框架的集成下使用的。
/**
*
* @description: 封裝Hibernate原生API的DAO泛型基類.
*
* 可在Service層直接使用, 也可以擴展泛型DAO子類使用, 見兩個構造函數的注釋。
*
* @author: liuhongwei
* @param <T>
* DAO操作的對象類型
* @param <PK>
* 主鍵類型
*/
@SuppressWarnings("unchecked")
public class BaseHibernateDao<T, PK extends Serializable> {
protected Logger logger = LoggerFactory.getLogger(getClass());
protected SessionFactory sessionFactory;
protected Class<?> entityClass;
/**
* 用於Dao層子類使用的構造函數.
* 通過子類的泛型定義取得對象類型Class.
* eg: public class UserDao eTtends BaseHibernateDao<User, Long>
*/
public BaseHibernateDao() {
this.entityClass = ReflectUtil.getSuperClassGenricType(getClass());
}
/**
* 用於省略Dao層, 在Service層直接使用通用BaseHibernateDao的構造函數.
* 在構造函數中定義對象類型Class
*/
public BaseHibernateDao(final SessionFactory sessionFactory, final Class<T> entityClass) {
this.sessionFactory = sessionFactory;
this.entityClass = entityClass;
}
/**
* 取得sessionFactory.
*/
public SessionFactory getSessionFactory() {
return sessionFactory;
}
/**
* 采用@Autowired按類型注入SessionFactory, 當有多個SesionFactory的時候在子類重載本函數.
*/
@Autowired
public void setSessionFactory(final SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
/**
* 取得當前Session.
*/
public Session getSession() {
return sessionFactory.getCurrentSession();
}
/**
* 保存新增或修改的對象.
*/
public void save(final T entity) {
Assert.notNull(entity, "entity不能為空");
getSession().saveOrUpdate(entity);
logger.debug("save entity: {}", entity);
}
/**
* 刪除對象.
*
* @param entity
* 對象必須是session中的對象或含id屬性的transient對象.
*/
public void delete(final T entity) {
Assert.notNull(entity, "entity不能為空");
getSession().delete(entity);
logger.debug("delete entity: {}", entity);
}
/**
* 按id刪除對象.
*/
public void delete(final PK id) {
Assert.notNull(id, "id不能為空");
delete(get(id));
logger.debug("delete entity {},id is {}", entityClass.getSimpleName(), id);
}
/**
* 按id獲取對象.
*/
public T get(final PK id) {
Assert.notNull(id, "id不能為空");
return (T) getSession().get(entityClass, id);
}
/**
* 按id列表獲取對象列表.
*/
public List<T> get(final Collection<PK> ids) {
return find(Restrictions.in(getIdName(), ids));
}
/**
* 獲取全部對象.
*/
public List<T> getAll() {
return find();
}
/**
* 獲取全部對象, 支持按屬性排序.
*/
public List<T> getAll(String orderByProperty, boolean isAsc) {
Criteria c = createCriteria();
if (isAsc) {
c.addOrder(Order.asc(orderByProperty));
} else {
c.addOrder(Order.desc(orderByProperty));
}
return c.list();
}
/**
* 按屬性查找對象列表, 匹配方式為相等.
*/
public List<T> findBy(final String propertyName, final Object value) {
Assert.hasText(propertyName, "propertyName不能為空");
Criterion criterion = Restrictions.eq(propertyName, value);
return find(criterion);
}
/**
* 按屬性查找唯一對象, 匹配方式為相等.
*/
public T findUniqueBy(final String propertyName, final Object value) {
Assert.hasText(propertyName, "propertyName不能為空");
Criterion criterion = Restrictions.eq(propertyName, value);
return (T) createCriteria(criterion).uniqueResult();
}
/**
* 執行HQL進行批量修改/刪除操作.
*
* @param values
* 數量可變的參數,按順序綁定.
* @return 更新記錄數.
*/
public int batchETecute(final String hql, final Object... values) {
return createQuery(hql, values).executeUpdate();
}
/**
* 執行HQL進行批量修改/刪除操作.
*
* @param values
* 命名參數,按名稱綁定.
* @return 更新記錄數.
*/
public int batchETecute(final String hql, final Map<String, ?> values) {
return createQuery(hql, values).executeUpdate();
}
/**
* 對entity的批量新增/修改操作
*
* @param entitys
* 實體列表
*/
public void batchUpdate(final List<?> entitys) {
Assert.notNull(entitys, "entitys不能為空");
Session session = getSession();
int count = 0;
for (Object obj : entitys) {
session.save(obj);
if (++count % 50 == 0) {
session.flush();
session.clear();
}
}
}
/**
* 根據查詢HQL與參數列表創建Query對象. 與find()函數可進行更加靈活的操作.
*
* @param values
* 數量可變的參數,按順序綁定.
*/
public Query createQuery(final String queryString, final Object... values) {
Assert.hasText(queryString, "queryString不能為空");
Query query = getSession().createQuery(queryString);
if (values != null) {
for (int i = 0; i < values.length; i++) {
query.setParameter(i, values[i]);
}
}
return query;
}
/**
* 根據查詢HQL與參數列表創建Query對象. 與find()函數可進行更加靈活的操作.
*
* @param values
* 命名參數,按名稱綁定.
*/
public Query createQuery(final String queryString, final Map<String, ?> values) {
Assert.hasText(queryString, "queryString不能為空");
Query query = getSession().createQuery(queryString);
if (values != null) {
query.setProperties(values);
}
return query;
}
/**
* 按Criteria查詢對象列表.
*
* @param criterions
* 數量可變的Criterion.
*/
public List<T> find(final Criterion... criterions) {
return createCriteria(criterions).list();
}
/**
* 按Criteria查詢唯一對象.
*
* @param criterions
* 數量可變的Criterion.
*/
public T findUnique(final Criterion... criterions) {
return (T) createCriteria(criterions).uniqueResult();
}
/**
* 根據Criterion條件創建Criteria. 與find()函數可進行更加靈活的操作.
*
* @param criterions
* 數量可變的Criterion.
*/
public Criteria createCriteria(final Criterion... criterions) {
Criteria criteria = getSession().createCriteria(entityClass);
for (Criterion c : criterions) {
criteria.add(c);
}
return criteria;
}
/**
* 初始化對象. 使用load()方法得到的僅是對象ProTy, 在傳到View層前需要進行初始化. 如果傳入entity,
* 則只初始化entity的直接屬性,但不會初始化延遲加載的關聯集合和屬性. 如需初始化關聯屬性,需執行:
* Hibernate.initialize(user.getRoles()),初始化User的直接屬性和關聯集合.
* Hibernate.initialize(user.getDescription()),
* 初始化User的直接屬性和延遲加載的Description屬性.
*/
public void initProTyObject(Object proTy) {
Hibernate.initialize(proTy);
}
/**
* Flush當前Session.
*/
public void flush() {
getSession().flush();
}
/**
* 為Query添加distinct transformer. 預加載關聯對象的HQL會引起主對象重復, 需要進行distinct處理.
*/
public Query distinct(Query query) {
query.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
return query;
}
/**
* 為Criteria添加distinct transformer. 預加載關聯對象的HQL會引起主對象重復, 需要進行distinct處理.
*/
public Criteria distinct(Criteria criteria) {
criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
return criteria;
}
/**
* 取得對象的主鍵名.
*/
public String getIdName() {
ClassMetadata meta = getSessionFactory().getClassMetadata(entityClass);
return meta.getIdentifierPropertyName();
}
/**
* 判斷對象的屬性值在數據庫內是否唯一.
*
* 在修改對象的情景下,如果屬性新修改的值(value)等於屬性原來的值(orgValue)則不作比較.
*/
public boolean isPropertyUnique(final String propertyName, final Object newValue, final Object oldValue) {
if (newValue == null || newValue.equals(oldValue)) {
return true;
}
Object object = findUniqueBy(propertyName, newValue);
return (object == null);
}
}
由此我們看出hibernate操作數據庫的優勢不言而喻,簡要說明:
①對JDBC的代碼進行了封裝,使我們的編程更簡便了,不用寫SQL語句,提高了開發效率。
②消除了代碼的映射規則,也無需在管理數據庫連接,全部被分離到了XML或注解里面去配置。
③hibernate使用的是HQL語言,它支持方言配置,方便數據庫移植。
④一個會話中,不需要操作多個對象,只要操作Session即可,關閉資源也只要關閉一個Session即可,當然在Spring的集成使用下這些都會交由Spring來管理。
hibernate的優點一大堆,那它有沒有缺點呢,答案是肯定的,凡事有利有弊嘛,那么它的缺點是什么呢?
①當我們更新時將發送所有的字段,而當我們查詢時它也會將我們不想要查詢的字段也查詢出來,這即是全表映射所帶來的麻煩。
②由於有第一點缺點,也就導致了我們無法根據不同的條件組裝我們需要的SQL語句。
③hibernate並不能很好的支持存儲過程,一大遺憾。
④對多表關聯和復雜SQL查詢支持稍差,還是要自己寫SQL語句,返回的結果,需要自己組裝為POJO。
⑤雖然hibernate使用的是HQL語言查詢,但是性能不高。而當我們的數據量很大或是大型系統時,必定需要優化SQL語句,同樣由於第一點缺點,hibernate無法做到。
⑥由於hibernate的高門檻,要完全掌握並不簡單,所以對於一個開始並不熟悉hibernate開發的人,學習時間稍長,開發速度稍慢。
3、Spring集成mybatis操作數據庫 由於hibernate自身的缺陷,因此又出現了一個新的ORM框架,即mybatis。這是一個半自動化的框架,何謂半自動,因為它需要手工編寫POJO、SQL和映射關系。而對於像我這種喜歡自己編寫SQL語句,就很喜歡這個框架,雖然要多花點時間編寫SQL語句,但至少在優化方面可以省心不少。如果單說mybatis的操作,似乎並沒多大意義。現在來看下Spring集成mybatis是如何操作數據庫的,首先是基礎配置文件mybatis_config.xml,如下所示<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties>
<!-- 配置數據庫方言 目前只有mysql和oracle兩種-->
<property name="dialect" value="mysql" />
</properties>
<settings>
<!-- 使用jdbc的getGeneratedKeys獲取數據庫自增主鍵值 -->
<setting name="useGeneratedKeys" value="true" />
<!-- 使用列別名替換列名 默認:true -->
<setting name="useColumnLabel" value="true" />
<!-- 開啟駝峰命名轉換:Table{create_time} -> Entity{createTime} -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 啟用或禁用 緩存 -->
<setting name="cacheEnabled" value="true" />
<!-- 啟用或禁用延遲加載。當禁用時, 所有關聯對象都會即時加載 -->
<!-- <setting name="lazyLoadingEnabled" value="true" /> -->
</settings>
<!--
設置別名
通過一個簡單的別名來表示一個冗長的類型,這樣可以降低復雜度。類型別名標簽typeAliases中可以包含多個typeAlias
-->
<typeAliases>
<typeAlias alias="user" type="com.lhw.test.user.entity.User" />
</typeAliases>
<!-- 引入映射文件 -->
<mappers>
<!-- start base config -->
<mapper resource="user/user.mapper.xml" />
</mappers>
</configuration>
既然是Spring集成mybatis操作數據庫,那么也有必要說下spring和jdbc的配置文件,它們分別是applicationContext-dao.xml和jdbc.properties,首先看applicationContext-dao.xml,代碼如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">
<!-- 開啟注解掃描 -->
<context:annotation-config />
<context:component-scan base-package="com.lhw.*" />
<!-- jdbc.properties文件路徑 -->
<context:property-placeholder location="classpath:jdbc.properties" ignore-unresolvable="true" />
<!-- 系統日志初始化配置類 -->
<bean id="log4jInitialization" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetClass" value="org.springframework.util.Log4jConfigurer" />
<property name="targetMethod" value="initLogging" />
<property name="arguments">
<list>
<value>classpath:log4j.xml</value>
</list>
</property>
</bean>
<!-- 數據庫日志初始化配置類 -->
<bean id="log4jdbcInterceptor" class="net.sf.log4jdbc.DataSourceSpyInterceptor" />
<bean id="dataSourceLog4jdbcAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="interceptorNames">
<list>
<value>log4jdbcInterceptor</value>
</list>
</property>
<property name="beanNames">
<list>
<value>dataSource</value>
</list>
</property>
</bean>
<!-- 數據源的配置 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- 設置連接池初始值 -->
<property name="initialSize" value="1" />
<!-- 設置連接池最大值 -->
<property name="maxActive" value="2" />
<!-- 設置連接池最大空閑值 -->
<property name="maxIdle" value="2" />
<!-- 設置連接池最小空閑值 -->
<property name="minIdle" value="1" />
<!--removeAbandoned: 是否自動回收超時連接-->
<property name="removeAbandoned" value="true"/>
<!--removeAbandonedTimeout: 超時時間(以秒數為單位)-->
<property name="removeAbandonedTimeout" value="180"/>
<!--maxWait: 超時等待時間以毫秒為單位 6000毫秒/1000等於60秒-->
<property name="maxWait" value="3000"/>
<!-- SQL查詢,用來驗證從連接池取出的連接 -->
<property name="validationQuery" value="SELECT 1" />
<property name="testOnBorrow" value="true" />
<!-- 指明連接是否被空閑連接回收器(如果有)進行檢驗,如果檢測失敗,則連接將被從池中去除 -->
<property name="testWhileIdle" value="true" />
<!-- 在每次空閑連接回收器線程(如果有)運行時檢查的連接數量,最好和maxActive一致 -->
<property name="numTestsPerEvictionRun" value="2"/>
<!-- 在空閑連接回收器線程運行期間休眠的時間值,以毫秒為單位,一般比minEvictableIdleTimeMillis小 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 連接池中連接,在時間段內一直空閑,被逐出連接池的時間(1000*60*60),以毫秒為單位 -->
<property name="minEvictableIdleTimeMillis" value="300000" />
</bean>
<!-- MyBatis sqlSessionFactory 配置 mybatis -->
<bean name="sqlSessionFactory" id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:config/mybatis_config.xml" />
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg ref="sqlSessionFactory"/>
</bean>
<!-- 基於接口方式 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<property name="basePackage" value="com.lhw.test.service"></property>
<property name="annotationClass" value="org.springframework.stereotype.Repository"></property>
</bean>
<!-- 事務控制 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="globalRollbackOnParticipationFailure" value="false"></property>
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<aop:aspectj-autoproxy proxy-target-class="true" />
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true" />
<tx:method name="query*" read-only="true" />
<tx:method name="save*" read-only="true" />
<tx:method name="update*" read-only="true" />
<tx:method name="del*" read-only="false" rollback-for="Exception" propagation="REQUIRED" />
<tx:method name="remove*" read-only="false" rollback-for="Exception" propagation="REQUIRED" />
<tx:method name="update*" read-only="false" rollback-for="Exception" propagation="REQUIRED" />
<tx:method name="modfiy*" read-only="false" rollback-for="Exception" propagation="REQUIRED" />
<tx:method name="add*" read-only="false" rollback-for="Exception" propagation="REQUIRED" />
<tx:method name="create*" read-only="false" rollback-for="Exception" propagation="REQUIRED" />
<tx:method name="insert*" read-only="false" rollback-for="Exception" propagation="REQUIRED" />
<tx:method name="*" read-only="false" rollback-for="Exception" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="serviceOperation" expression="execution(* com.lhw.test..service..*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceOperation" />
</aop:config>
</beans>
再看jdbc.properties,代碼如下:
#jdbc settings
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&noAccessToProcedureBodies=true
jdbc.username=root
jdbc.password=root
接下來是一個映射文件user.mapper.xml,也很簡單,如下所示。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="userMapper" >
<!--
如果POJO的屬性名與數據庫中表中列名映射一致,也可不寫,或是通過在POJO中通過@Column(name = "")將數據庫字段注解到POJO屬性或方法上保持一致性也可
-->
<resultMap id="userMap" type="user" >
<id column="id" property="id" jdbcType="BIGINT" />
<result column="username" property="username" jdbcType="VARCHAR" />
<result column="password" property="password" jdbcType="VARCHAR" />
<result column="name" property="name" jdbcType="VARCHAR" />
<result column="sex" property="sex" jdbcType="INTEGER" />
<result column="email" property="email" jdbcType="VARCHAR" />
<result column="tel" property="tel" jdbcType="VARCHAR" />
</resultMap>
<sql id="Base_Column_List" >
id,username,password,name,sex,email.tel
</sql>
<!-- 根據id查詢 -->
<select id="queryById" resultMap="userMap" parameterType="java.lang.Long" >
select
<include refid="Base_Column_List" />
from b_user where id = #{id,jdbcType=BIGINT}
</select>
<!-- 查詢所有 -->
<select id="queryAll" resultMap="userMap">
select
<include refid="Base_Column_List" />
from tb_user where 1 = 1
</select>
</mapper>
然后我們再使用Spring為我們提供的操作模板sqlSessionTemplate,代碼如下:
/**
*
* @description:獲取sqlSessionTemplate
*
* @author: liuhongwei
*/
public class BaseMybatisDao {
@Resource
public SqlSession sqlSessionTemplate;
}
最后我們可以通過繼承這個BaseMybatisDao進行CRUD操作了,代碼如下:
/**
*
* @description: 用戶類數據庫操作
*
* @author: liuhongwei
*/
@Repository("userDao")
public class UserDao extends BaseMybatisDao {
/**
* 根據id查詢用戶信息
* @param id
* @return
*/
@Override
public Email queryById(Integer id) {
return sqlSessionTemplate.selectOne("userMapper.queryById",id);
}
/**
* 查詢所有
* @return
*/
@Override
public List<User> queryAll() {
return sqlSessionTemplate.selectList("userMapper.queryAll");
}
三、選擇JDBC、hibernate還是mybatis
通過JDBC、hibernate和mybatis三種操作數據庫的簡易CRUD例子,我們知道,不管是哪種方式,都有利有弊。那么我們應該選擇哪種方式好呢?其實這個沒有一種最適合的方案,而我平常在開發中一般至少會把hibernate和mybatis兩種ORM框架集成進去,甚至有時JDBC這種方式也會集成進去,這主要是依據項目實際情況而定,但是我並不推薦使用JDBC這種方式。
hibernate作為一個流行且自動化的ORM框架,編程簡單,無需編寫SQL語句,可以使用代碼生成工具生成entity及dao層代碼,並提供強大的緩存、級聯和日志功能。但是由於其自動化缺陷,導致我們無法控制SQL語句,使得性能無法得到有效優化,並且其不適應存儲過程,所以hibernate也只適應於對性能要求不是太高且場景不太復雜的系統。
而mybatis作為一個半自動化的框架,由於其高度靈活,自由控制SQL語句,支持動態SQL語句和存儲過程,同樣也可以通過代碼生成工具生成entity和dao層,當然如果自己封裝模板的話,對於entity中屬性和方法名以及dao、service、controller層簡易的增刪改查都可以自動生成。對於大型移動互聯網項目以及需要考慮查詢性能優化的都可以采用mybatis這種方式。