# ORM
# Flag
对象关系映射(Object Relational Mapping,简称ORM)是通过使用描述对象和数据库之间映射的元数据, 将面向对象语言程序中的对象自动持久化到关系数据库中。
- https://sourceforge.net/projects/jpox (opens new window)
- Java,JDBC和MySQL对应数据类型 (opens new window)
- http://www.squirrelsql.org (opens new window)
- http://java-source.net/open-source/sql-clients (opens new window)
- JPA标准API https://github.com/Blazebit/blaze-persistence (opens new window)
- 链式SQL框架 https://github.com/jOOQ (opens new window)
- https://github.com/querydsl/querydsl (opens new window)
- https://github.com/BatooOrg/BatooJPA (opens new window)
- https://github.com/aaberg/sql2o (opens new window)
- https://github.com/jdbi/jdbi (opens new window)
- toplink https://github.com/eclipse-ee4j/eclipselink (opens new window)
- https://github.com/datanucleus (opens new window)
- https://github.com/apache/openjpa (opens new window)
- https://github.com/spring-projects/spring-data-jdbc (opens new window)
- https://github.com/apache/commons-dbutils (opens new window)
- https://github.com/sagframe/sagacity-sqltoy (opens new window)
- 元数据操作 https://github.com/schemacrawler/SchemaCrawler (opens new window)
- 只读查询 https://github.com/ejlchina/bean-searcher (opens new window)
- https://github.com/speedment (opens new window)
- https://github.com/troyzhxu/bean-searcher (opens new window)
- https://github.com/MyCATApache/Mycat2 (opens new window)
- CRUD生成器 https://github.com/SanjinKurelic/MVCGenerator (opens new window)
- https://github.com/jeddict/jeddict (opens new window)
- https://codverter.com/src/sqltoclass (opens new window)
- https://gitee.com/l0km/sql2java (opens new window)
- https://github.com/blinkfox/zealot (opens new window)
- https://github.com/vipbooks/TableGo (opens new window)
- https://github.com/jahlborn/sqlbuilder (opens new window)
- 表结构文档生成 https://github.com/pingfangushi/screw (opens new window)
- 数据库中间件 https://github.com/apache/shardingsphere (opens new window)
- 数据库动态监控 https://github.com/p6spy/p6spy (opens new window)
- https://github.com/jdbc-observations (opens new window)
SQL解析和验证器
- https://github.com/apache/calcite (opens new window)
- https://github.com/JSQLParser/JSqlParser (opens new window)
- https://github.com/prestodb/presto (opens new window)
- https://github.com/camertron/SQLParser (opens new window)
- https://github.com/iByteCoding/M-SQLParser (opens new window)
- https://github.com/jparsec/jparsec (opens new window)
- https://github.com/manasesjesus/fdbs-and-sql-parser (opens new window)
- https://github.com/liweihua274/druid-sqlparser (opens new window)
连接池
- https://github.com/brettwooldridge/HikariCP (opens new window)
- https://github.com/alibaba/druid (opens new window)
- https://github.com/apache/tomcat/tree/main/modules/jdbc-pool (opens new window)
- https://github.com/apache/commons-dbcp (opens new window)
- https://github.com/apache/commons-pool (opens new window)
- https://github.com/agroal/agroal (opens new window)
- https://github.com/vibur (opens new window)
- https://github.com/Chris2018998/BeeCP (opens new window)
- https://sourceforge.net/projects/proxool (opens new window)
- https://github.com/swaldman/c3p0 (opens new window)
- https://github.com/wwadge/bonecp (opens new window)
Transaction
- https://github.com/atomikos/transactions-essentials (opens new window)
- https://github.com/codingapi/tx-lcn (opens new window)
- https://github.com/seata/seata (opens new window)
- https://github.com/changmingxie/tcc-transaction (opens new window)
- https://github.com/QNJR-GROUP/EasyTransaction (opens new window)
- https://github.com/liuyangming/ByteTCC (opens new window)
- https://github.com/wchswchs/Hulk (opens new window)
- https://github.com/atomikos/transactions-essentials (opens new window)
- https://github.com/bitronix/btm (opens new window)
- 分布式事务 XA 两段式事务 X/open CAP BASE 一次分清 (opens new window)
- X/Open https://publications.opengroup.org/s243 (opens new window)
# JDBC驱动
注意使用的MySQL Connector/J驱动
org.gjt.mm.mysql.Driver
用于3.x
版本之前,在5.1.47
以上版本不存在该类,由一个名为MM (Monty's MySQL) 的项目开发的,该项目后来被Sun Microsystems收购com.mysql.jdbc.Driver
用于5.1.6
版本之前,在MySQL AB(后被Oracle Corporation收购)接手并继续开发JDBC驱动之后使用的com.mysql.cj.jdbc.Driver
用于5.1.6
版本之后
从Java 6
JDBC 4.0
开始,显式加载驱动类的步骤Class.forName(…)
通常是不必要的,因为JDBC服务提供者机制会自动加载驱动
URL及参数
- [(https://mysql-net.github.io/MySqlConnector/connection-options]((https://mysql-net.github.io/MySqlConnector/connection-options)
jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
参数名称 | 参数说明 | 缺省值 | 最低版本要求 |
---|---|---|---|
user | 数据库用户名(用于连接数据库) | 所有版本 | |
password | 用户密码(用于连接数据库) | 所有版本 | |
useUnicode | 是否使用Unicode字符集,使用参数characterEncoding,本参数值必须设置为true | FALSE | 1.1g |
characterEncoding | 当useUnicode设置为true时,指定字符编码。比如可设置为gb2312或gbk、UTF-8 | FALSE | 1.1g |
autoReconnect | 当数据库连接异常中断时,是否自动重新连接? | FALSE | 1.1 |
autoReconnectForPools | 是否使用针对数据库连接池的重连策略 | FALSE | 3.1.3 |
failOverReadOnly | 自动重连成功后,连接是否设置为只读? | TRUE | 3.0.12 |
maxReconnects | autoReconnect设置为true时,重试连接的次数 | 3 | 1.1 |
initialTimeout | autoReconnect设置为true时,两次重连之间的时间间隔,单位:秒 | 2 | 1.1 |
connectTimeout | 和数据库服务器建立socket连接时的超时,单位:毫秒 | 0 | 3.0.1 |
socketTimeout | socket操作(读写)超时,单位:毫秒。 0表示永不超时 | 0 | 3.0.1 |
useSSL | 是否进行ssl连接 | ||
zeroDateTimeBehavior | 把零值日期转换为null | ||
serverTimezone | GMT%2B8 %2B 是+ 的转义字符,其实就是GMT+8 ('+8:00'),代表东八区Asia/Shanghai |
useTimeZone
为true时,会开启服务器和客户端之间的时区转换,只有useLegacyDatetimeCode = true
时才回生效useLegacyDatetimeCode
默认为true,为false时:useTimezone
,useJDBCCompliantTimezoneShift
,useGmtMillisForDatetimes
,useFastDateParsing
这几个参数都会无效
# Mybatis
- https://github.com/mybatis (opens new window)
- https://github.com/pagehelper/Mybatis-PageHelper (opens new window)
- https://github.com/baomidou/mybatis-plus (opens new window)
- https://github.com/mybatis-mapper (opens new window)
- https://github.com/abel533/Mapper (opens new window)
- https://github.com/alexdyysp/MybatisHandBook (opens new window)
- https://github.com/fengwenyi/mybatis-plus-code-generator (opens new window)
- https://github.com/itfsw/mybatis-generator-plugin (opens new window)
- 干掉mapper.xml!MyBatis新特性动态SQL (opens new window)
- Mybatis 源码分析 (一) Mapper扫描及代理 (opens new window)
- 一条sql查出树形结构数据 (opens new window)
- Sql中对于树形结构的处理 (opens new window)
trim标签
属性 | 描述 |
---|---|
prefix | 在条件语句前需要加入的内容 |
suffix | 在条件语句后需要加入的内容 |
prefixOverrides | 覆盖/去除sql语句前面的指定内容 |
suffixOverrides | 覆盖/去除sql语句后面的指定内容 |
- 示例
<select id="selectSelective" resultType="java.util.Map"> SELECT * FROM user <trim prefix="WHERE" suffix="AND|OR"> <if test="id != null and id.trim() != '' "> id = #{id} AND </if> <if test="name != null and name.trim() != '' "> name = #{name} AND </if> </trim> </select> <!-- 等同于以下方式 --> <select id="selectSelective" resultType="java.util.Map"> SELECT * FROM user <where> <if test="id != null and id.trim() != '' "> id = #{id} </if> <if test="name != null and name.trim() != '' "> AND name = #{name} </if> </where> </select>
Copied!
Oracle 的in query list 的大小要不大于1000
List<String> taskLists = new ArrayList<>(); taskLists.addAll(tasksToArchive); int times = tasksToArchive.size() % 1000 == 0 ? tasksToArchive.size() / 1000 : (tasksToArchive.size() / 1000 + 1); List<IAccountingTask> tasksToArchiveList = new ArrayList<IAccountingTask>(); if (taskLists != null && !taskLists.isEmpty()) { for (int i = 0; i < times; i++) { List<? extends IAccountingTask> tempList; if ((i + 1) * 1000 <= taskLists.size()) { tempList = persistenceManager.getSession().getAll(AccountingTask.class, taskLists.subList(i * 1000, (i + 1) * 1000)); } else { tempList = persistenceManager.getSession().getAll(AccountingTask.class, taskLists.subList(i * 1000, taskLists.size())); } tasksToArchiveList.addAll(tempList); } } List<TransactRepViewModel> result = new ArrayList<TransactRepViewModel>(); final List<List<String>> partitions = ListUtils.partition(clientIdList, 999); for (List<String> partition : partitions) { result.addAll(yourRepo.findByClientIdList(partition, startDate, endDate);) }
Copied!
<foreach collection="sessionIds" item="session_id" open="(" close=")" separator="," index="index"> <if test="index == 0"> session_id in ( </if> <!-- 多个if判断原因: sql in 最大只有1000个参数。 foreach 只去掉了最后一个‘,’ 数据库表中sessionId是非空设置 --> <if test="index != 0 and index % 500 == 0"> '' ) or session_id in ( </if> #{session_id} <if test="index == sessionIds.size - 1"> ) </if> </foreach>
Copied!
# Hibernate
- https://github.com/hibernate (opens new window)
- https://github.com/vladmihalcea/hypersistence-optimizer (opens new window)
- https://github.com/jeecgboot/MiniDao (opens new window)
- https://github.com/AnghelLeonard/Hibernate-SpringBoot (opens new window)
# 返回结果接收方式
setresulttransformer与addentity的区别
一个区别是前者支持查任意的列,后者必须用select * from users的形式或select {a.},{b.} from a,b where ....。
如果使用原生sql语句进行query查询时,hibernate是不会自动把结果包装成实体的。所以要手动调用addentity(class class)等一系列方法。
如session.createsqlquery(sql).addentity(class class);注意hibernate3.0.5不支持,单个参数的addentity方法
另外,hibernate3.2可以对原生sql 查询使用resulttransformer。这会返回不受hibernate管理的实体。
session.createsqlquery("select name ,age,birthday from students") .setresulttransformer(transformers.aliastobean(students.class)) // 或 setresulttransformer(new aliastobeanresulttransformer (students.class))
Copied!
上面的查询将会返回students的列表,它将被实例化并且将name和birthday的值注射入对应的属性或者字段。 但必须注意,对每一个列都必须addscalar("列名")
返回结果转换为Map
List<Map<String, Object>> list = session.createSQLQuery(sql).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP).list(); Map<String, Object> map = (Map<String, Object>) session.createSQLQuery(sql).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP).uniqueResult();
Copied!
当确定返回的实例只有一个或者null时 用uniqueResult()方法
Double result = (Double) session.createSQLQuery(querySql).uniqueResult();
Copied!
# HQL查询
String hql = "from Invest i where i.loan.id=? and i.status=?"; List<Invest> is = ht.find(hql, new String[] { loanId, InvestStatus.REPAYING }); // 或者 String hql = "from Invest i where i.loan.id=? and i.status=?"; List<Invest> is = ht.find(hql, loanId, InvestStatus.REPAYING ); // 使用in,同createSQLQuery使用方式一样 String hql = "from Invest i where i.loan.id=? and i.status in(:status)"; List<Invest> is = session.createQuery(sql) .setParameter(0, loanId) .setParameterList("status", Arrays.asList(InvestStatus.REPAYING, InvestStatus.OVERDUE, InvestStatus.COMPLETE, InvestStatus.BID_SUCCESS)).list()
Copied!
# QBC查询
该方式只能用关联表的关联字段为查询条件,无法使用关联表的其他字段为查询条件
Criteria 和 DetachedCriteria 的主要区别在于创建的形式不一样, Criteria 是在线的,所以它是由hibernate Session 进行创建的; 而 DetachedCriteria 是离线的,创建时无需Session ,DetachedCriteria 提供了 2 个静态方法 forClass(Class) 或 forEntityName(Name)进行DetachedCriteria 实例的创建。
spring 的框架提供了getHibernateTemplate().findByCriteria(detachedCriteria) 方法可以很方便地根据DetachedCriteria 来返回查询结果。
hibernate5.2版本之前createCriteria()查询的方式
// Restrictions.in传值为数组或list集合 List<String> status = Arrays.asList(InvestConstants.InvestStatus.REPAYING,InvestConstants.InvestStatus.OVERDUE); DetachedCriteria criteria = DetachedCriteria.forClass(InvestExtensionPlan.class); criteria.createAlias("invest", "i");// 当查询关联第三张表时,第二张表需要取别名 criteria.add(Restrictions.eq("i.loan.id", loanExtensionPlan.getLoan().getId())); criteria.add(Restrictions.in("status", status)); // 可解决ORACLE in 大于1000问题 criteria.addOrder(Order.desc("period"));// 添加排序 // 查询一范围内的的数据,需借助Criteria来查询 Criteria cri = criteria.getExecutableCriteria(ht.getSessionFactory().getCurrentSession()); cri.setFirstResult(firstResult);// 从第几条开始 cri.setMaxResults(maxResults);// 查询多少条 List<InvestExtensionPlan> investExtensionPlans = ht.findByCriteria(criteria); // 可解决ORACLE in 大于1000问题 private void addCriteriaIn(String propertyName, List<?> list, Criteria criteria) { Disjunction or = Restrictions.disjunction(); if (list.size() > 1000) { while (list.size() > 1000) { List<?> subList = list.subList(0, 1000); or.add(Restrictions.in(propertyName, subList)); list.subList(0, 1000).clear(); } } or.add(Restrictions.in(propertyName, list)); criteria.add(or); }
Copied!
模糊查询和自定义查询
criteria.add(Restrictions.like("time","%" + "2018-11-13" + "%")); criteria.add(Restrictions.sqlRestriction("time like '%2018-11-13%'"));
Copied!
hibernate5.2及之后版本createCriteria()查询的方式
原有的session.createCriteria()方法已经过时,替代的方式是使用JPA Criteria。
session.createSQLCriteria()方法也过时了,当然可以用session.createNativeCriteria()方法来代替。
//注意导入的包是import javax.persistence.criteria.CriteriaQuery; try { session.beginTransaction(); //1.创建CriteriaBuilder,用于创建查询 CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder(); //2.创建CriteriaQuery,用于设置查询语句的信息 CriteriaQuery<Student> criteriaQuery = criteriaBuilder.createQuery(Student.class); //3.定义查询的From子句中能出现的类型,也可以用root.get()获取具体的某个属性 Root<Student> root = criteriaQuery.from(Student.class); //4.设置WHERE字句的条件,此处条件为score<= 98 criteriaQuery.where(criteriaBuilder.lt(root.get("score"), 98)); //5.设置排序标准与排序方式 criteriaQuery.orderBy(criteriaBuilder.desc(root.get("score"))); //6.创建Query对象并获取结果集list Query<Student> query = session.createQuery(criteriaQuery); List<Student> list = query.list(); // List<Student> list = session.createQuery(criteriaQuery).getResultList(); //7.遍历结果集 list.forEach(System.out::println); session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); }
Copied!
多表查询
try { session.beginTransaction(); //1.创建CriteriaBuilder CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder(); //2.创建CriteriaQuery CriteriaQuery<Country> criteriaQuery = criteriaBuilder.createQuery(Country.class); //3.设置distinct去重 criteriaQuery.distinct(true); //4.获取root句柄 Root<Country> root = criteriaQuery.from(Country.class); //5.设置fetch的连接对象以及连接类型,此处为迫切左外连接 root.fetch("ministers", JoinType.LEFT); //6.设置where查询条件 criteriaQuery.where(criteriaBuilder.equal(root.get("cid"), 1)); //7.获取Query对象 Query<Country> query = session.createQuery(criteriaQuery); //8.获取查询结构 List<Country> list = query.list(); // List<Student> list = session.createQuery(criteriaQuery).getResultList(); for (Country country : list) { System.out.println(country); country.getMinisters().forEach(System.out::println); } session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); }
Copied!
# 关闭session
if (session != null) { session.flush(); session.clear(); session.close(); }
Copied!
- hibernate中evict()和clear()的区别
session.evict(obj):会把指定的缓冲对象从一级缓存中进行清除;
session.clear():把一级缓存中的全部对象清除,但不包括操作中的对象。
- hibernate执行的顺序如下:
(1)生成一个事务的对象,并标记当前的session处于事务状态(此时并未启动数据库级事务)。
(2)应用使用s.save()保存对象,这个时候Session将这个对象放入entityEntries,用来标记对象已经和当前的会话建立了关联, 由于应用对对对象做了保存的操作,Session还要在insertions中登记应用的这个插入行为(行为包括:对象引用、对象id、Session、持久化处理类)。
(3)s.evict()将对象从s会话中拆离,这时s会从entityEntries中将这个对象移除。
(4)事务提示,需要将所有缓存flush入数据库,Session启动一个事务,并按照insert ,update,...,delete的顺序提交所有之前登记的操作 (注意:所有insert执行完毕后才会执行update,这里的特殊处理也可能会将你的程序搞得一团遭,如需要控制操作的顺序,需要使用flush), 现在对象不再entityEntries中,但在执行insert的行为时只需要访问insertions就足够了,所以此时不会有任何的异常, 异常出现在插入后通知Session该对象已经插入完毕这个步骤上,这个步骤中需要将entityEntries中对象的existsInDatabase标志置true, 由于对象并不存在于entityEntres中,此时Hibernate就认为insertions和entityEntries可能因为线程安全的问题产生了不同步 (也不知道Hibernate的开发者是否考虑到例子中的处理方式,如果没有的话,这也许算是一个bug吧), 于是一个net.sf.hibernate.AssertionFailure就被抛出,程序终止。
一般我们会错误的认为s.sava会立即执行,而将对象过早的与session拆离,造成了session的insertion和entityEntries中内容的不同步。 所以我们在做此类操作时一定要清楚hibernate什么时候会将数据flush入数据库,在未flush之前不要将已进行操作的对象从session上拆离,解决办法是在sava之后,添加session.flush。
/** * 验证缓存管理的方法evict * 执行完evict之后,将会将id为5的user对象从一级缓存中移除,再次访问的话将重新查询数据库 * 该用例将发出2个select语句 */ @Test public void testEvict(){ Session session = HibernateUtil.getSession(); User user1 = (User)session.get(User.class, 5); System.out.println(user1.getName()); session.evict(user1); User user2 = (User)session.get(User.class, 5); System.out.println(user2.getName()); session.close(); } /** * 验证缓存管理的方法clear * 执行clear方法之后,一级缓存中的对象全部被清除,再次查询,将从数据库中查询 * 该用例将发出2个select语句 */ @Test public void testClear(){ Session session = HibernateUtil.getSession(); User user1 = (User)session.get(User.class, 5); System.out.println(user1.getName()); System.out.println("======================="); User user2 = (User)session.get(User.class, 5); System.out.println(user2.getName()); session.clear(); }
Copied!
# 只读错误
Write operations are not allowed in read-only mode (FlushMode.MANUAL): Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition.
写操作在只读模式下不被允许(FlushMode.MANUAL): 把你的Session改成FlushMode.COMMIT/AUTO或者清除事务定义中的readOnly标记。
# 编程式修改FlushMode
ht.setFlushMode(HibernateTemplate.FLUSH_AUTO); ht.getSessionFactory().getCurrentSession().setFlushMode(FlushMode.AUTO);
Copied!
# 配置过滤器
<web-app version="3.0" > <filter> <filter-name>OpenSessionInViewFilter</filter-name> <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class> <!-- singleSession默认为true,若设为false则等于没用OpenSessionInView 。所以默认可以不写 --> <init-param> <param-name>singleSession</param-name> <param-value>true</param-value> </init-param> <!-- 设置flushMode为AUTO(在确保查询从不会返回脏数据的情况下,在查询前刷新Session。)/COMMIT(Session在提交事务时刷新。) --> <init-param> <param-name>flushMode</param-name> <param-value>AUTO</param-value> </init-param> </filter> <filter-mapping> <filter-name>OpenSessionInViewFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
Copied!
# 使用execute方法回调方式实现
- Dao层HibernateTemplate操作数据时,使用execute方法回调方式实现:
// 原方式 getHibernateTemplate().save(user); // 更改后方式 getHibernateTemplate().execute(new HibernateCallback<User>() { @Override public User doInHibernate(Session session) throws HibernateException { session.save(user); return null; } });
Copied!