从JDBC到hibernate再到mybatis之路

  

一、传统的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这种方式。

posted @ 2017-04-25 21:15  春江花月  阅读(2802)  评论(0编辑  收藏  举报