使用eclipse开发一个基于java的maven的web项目,业务逻辑基于spring实现,数据库服务器采用mysql。我们项目的业务逻辑(面向对象)和数据(关系型数据库)之间,依靠hibernate进行映射、对应、使用和处理。
hibernate的核心功能之一,就是在面向对象和关系型数据库之间建立转化和映射,让程序员用面向对象的方式看待关系型数据库。(让数据库对程序员透明)
开发过程中,为了保持二者的一致,需要进行Domain Object这种POJO的逆向生成。也就是依据mysql数据库中数据表,在eclipse这个IDE中,通过eclipse的Hibernate插件,生成Domain Object的java代码,这样就可确保二者保持一致。
eclipse上的配置操作如下所示:
在自己eclipse上market中确保install了jboss也就是hibernate的相关plugin
1---使用eclipse连接mysql
我们的开发环境中,mysql就部署在开发机本机,也就是说tomcat mysql eclipse三者都在同一台电脑上。
打开eclipse上的data source explorer窗口
最开始需要你自己新建一个Driver,也就是选一下你用的什么数据库,本地的jdbc jar文件,输入一下你以前事先,在mysql上配置好的url用户名密码之类的。
搞定以后,finish
可以看到我们的eclipse已经完全连接上了本地我们要是用的mysql中的test2数据库。
2---为eclipse上的web项目添加hibernate tools相关的配置文件
目的是让我们的web项目关联上mysql数据库,并且要通过hibernate tools进行关联。这一步最开始我遇到了麻烦,产生了报错,结果就是无法通过hibernate tools将web项目和mysql关联,好在我找到了原因,下面将整个过程详述一下。
在项目上右键,new,添加一个hibernate configuration file
下面Hibernate version这里需要注意,我们eclipse安装的JBoss的插件版本、pom中Hibernate的版本、本配置文件所使用的hibernate版本是否需要三者保持一致呢?答案是否定的。我们只需要保证pom中的版本和此处配置文件版本一致。
版本一致的鉴定方法如下图:pom与配置文件一致
反而是eclipse的jboss插件和hibernate没有什么直接的版本关系
点击finish以后,出现如下所示的报错:Reading schema error:Error calling Driver#connect
说实话这个报错英文字面意思比较清晰,但是实际对调试的指导意义为零,而且百度了一下也不知道怎么应对。
connect
最后我猜,应该是connector的问题
于是在上面几步connector那一步,修改了一下引用的jar文件
看,调通了。也就是说,我为我们的web项目添加一个hibernate tools配置文件,让web项目关联上了mysql数据库。
3--逆向生成java代码
然后点击run按钮
看,生成了很多java代码。有很多报错的原因,因为这个web项目是刚刚创建的maven web项目,还没有修改pom.xml文件,很多hibernate的jar包还没有导入。
但是逆向工程,已经成功生成了代码,哈哈
POM.xml修改如下
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>tsmi.com</groupId>
<artifactId>tangtools</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>tangtools Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.2.11.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.0-api</artifactId>
<version>1.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.common</groupId>
<artifactId>hibernate-commons-annotations</artifactId>
<version>5.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.2.11.Final</version>
</dependency>
</dependencies>
<build>
<finalName>tangtools</finalName>
</build>
</project>
在正式项目中,通过hibernate tools转化mysql对应的PO,最初效果如下:
结果很让人心痛,原因是target folder和package设置重复了,那么我们把关键步骤单独拿出来,看看应该怎样设置:
顺利生成PO文件以后,我们要将过去单独使用spring进行数据库连接和事务管理的配置文件修改为,spring+hibernate环境下的配置文件。另外过去采用dbcp的数据库封装,现在改用阿里巴巴的druid进行封装。过去直接将各种需要的参数用字符串的形式直接表现出来,现在将配置信息统一放置在properties文件中保存。
过去我们的spring的ApplicationContext.xml配置文件,就一个。
现在变成两个,并重新命名
mysqlHibernateContext.xml文件
mysqlHibernateContext.properties文件
如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 集成hibernate之后的Spring的applicationContext配置文件 -->
<!-- mysql dao和service层的Spring配置文件 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
classpath:/org/springframework/beans/factory/xml/spring-beans.xsd
http://www.springframework.org/schema/context
classpath:/org/springframework/context/config/spring-context.xsd
http://www.springframework.org/schema/aop
classpath:/org/springframework/aop/config/spring-aop.xsd
http://www.springframework.org/schema/tx
classpath:/org/springframework/transaction/config/spring-tx.xsd
http://www.springframework.org/schema/jdbc
classpath:/org/springframework/jdbc/config/spring-jdbc.xsd">
<context:component-scan base-package="com.tsmi.mysql.dao"/>
<context:component-scan base-package="com.tsmi.mysql.service"/>
<context:component-scan base-package="com.tsmi.mysql.web"/>
<!--配置文件-->
<bean id="propertyConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list> <value>classpath:/mysqlHibernateContext.properties</value>
</list>
</property>
</bean>
<!-- 配置mysql数据源, 阿里巴巴的druid-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close"
p:driverClassName="${jdbc.driver}"
p:url="${jdbc.url}"
p:username="${jdbc.username}"
p:password="${jdbc.password}"
p:initialSize="${ds.initialSize}"
p:minIdle="${ds.minIdle}"
p:maxActive="${ds.maxActive}"
p:maxWait="${ds.maxWait}"
p:timeBetweenEvictionRunsMillis="${ds.timeBetweenEvictionRunsMillis}"
p:minEvictableIdleTimeMillis="${ds.minEvictableIdleTimeMillis}"
p:removeAbandoned="${ds.removeAbandoned}"
p:removeAbandonedTimeout="${ds.removeAbandonedTimeout}"
p:defaultAutoCommit="true" />
<!-- spring为集成hibernate提供的LocalSessionFactoryBean -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
</props>
</property>
<property name="packagesToScan" value="com.tsmi.hibernate.entity"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionmanager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!-- 配置JdbcTemplate -->
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
properties文件
#database connection
jdbc.dbType=mysql
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:6062/test2?characterEncoding=utf8&serverTimezone=UTC
jdbc.username=root
jdbc.password=哈哈哈哈
sys.singleOnline=false
#hibernate property
ds.initialSize=1
ds.minIdle=1
ds.maxActive=20
ds.maxWait=60000
ds.timeBetweenEvictionRunsMillis=60000
ds.minEvictableIdleTimeMillis=300000
ds.removeAbandoned=true
ds.removeAbandonedTimeout=180
上面的配置文件修改以后,运行eclipse上以前的test文件,可以测试代码顺利运行。
上述过程,通过hibernate tools生成PO以后,我们做了哪些操作呢?
1---在Spring配置文件中,加入hibernate的配置信息;
2---在Spring配置文件中,将dbcp替换为druid
3---将Spring配置文件中的字符串,换成了properties文件中的变量;
但是上述过程中,出现了很多个报错,当我运行testNG中的测试代码时,有很多报错,调试报错的过程无法复述,简要列举一下:
1---java.lang.ClassNotFoundException:org.dom4j.io.STAXEventReader
解决办法,从新,单独再次在pom.xml文件中导入dom4j
2---java.lang.NoClassDefFoundError: javassist/util/proxy/MethodFilter
解决办法:
3---org.hibernate.type.StringType cannot be cast to org.hibernate.type.VersionType
检验标准这个表中原来有个字段叫做version,必须把这个名字改了,我改成了如图所示的verno
然后重新用powerDesigner生成数据库文件导入mysql,也就是把mysql的表结构重新生成一下
4---cannot convert sessionfactoryimpl datasource
反正我就把powerDesigner中所有表都过滤了一遍,只要是属性字段,必须全部小写:比如QC这个字段,改为qc
5---bean property is not writeable or has an invalid setter method
说实话我压根不知道自己怎么调好的,最后我把applicationContext.xml文件中事务相关的配置,从spring的事务修改为hibernate,然后就好了,费解。
反正,最终test代码可以用了。
下面,截图演示一下从powerDesigner绘制uml图到mysql生成数据库表,到eclipse中形成po代码的过程:
打开powerDesigner
生成
生成*.sql文件
文件长这个样子
在phpmyadmin中创建一个空的名为test2的数据库
进入导入页面
完成导入后在mysql中生成数据表,然后我们使用Navicat for MySQL这个软件,从事先准备好的excel表导入一些数据给mysql中刚刚创建名为test2的数据库。
中间出了错误,原因是字段长度,这个我们以后再改好了。看看效果
现在有了数据库数据表,也有了部分数据,方便今后进行验证。我们去eclipse的hibernate tools看看。
回到eclipse,之前如何安装jboss tools,如何使用data source explorer和hibernate configuration的过程不再赘述,假设现在已经成功配置,那么我们直接来生成po代码:
生成了代码
刚刚生成出来po代码的时候,如果直接启动tomcat,会有报错如下所示:
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
十二月 19, 2017 5:23:10 下午 org.springframework.web.context.support.XmlWebApplicationContext refresh
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory' defined in class path resource [mysqlHibernateContext.xml]: Invocation of init method failed; nested exception is java.lang.NoSuchMethodError: javax.persistence.JoinTable.indexes()[Ljavax/persistence/Index;
十二月 19, 2017 5:23:10 下午 com.alibaba.druid.pool.DruidDataSource info
信息: {dataSource-1} closed
十二月 19, 2017 5:23:10 下午 org.springframework.web.context.ContextLoader initWebApplicationContext
严重: Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory' defined in class path resource [mysqlHibernateContext.xml]: Invocation of init method failed; nested exception is java.lang.NoSuchMethodError: javax.persistence.JoinTable.indexes()[Ljavax/persistence/Index;
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1704)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory
Caused by: java.lang.NoSuchMethodError: javax.persistence.JoinTable.indexes()[Ljavax/persistence/Index;
at org.hibernate.cfg.AnnotationBinder.bindJoinedTableAssociation(AnnotationBinder.java:2380)
十二月 19, 2017 5:23:10 下午 org.apache.catalina.core.StandardContext listenerStart
严重: Exception sending context initialized event to listener instance of class [org.springframework.web.context.ContextLoaderListener]
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory' defined in class path resource [mysqlHibernateContext.xml]: Invocation of init method failed; nested exception is java.lang.NoSuchMethodError: javax.persistence.JoinTable.indexes()[Ljavax/persistence/Index;
警告: The web application [RepositoryCheck] appears to have started a thread named [Abandoned connection cleanup thread] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
java.lang.Object.wait(Native Method)
java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
com.mysql.jdbc.AbandonedConnectionCleanupThread.run(AbandonedConnectionCleanupThread.java:64)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
java.lang.Thread.run(Thread.java:748)
百度了一下java nosuchmethoderror javax persistence table indexes按照里面介绍的方法修改java代码如下图
验证,至少修改并停止使用@Table这个标签,改用@Entity标签以后,可以顺利启动Tomcat服务器。
但是上述解决问题的方法是十分残酷的,因为@Table标签代表的是mysql的表名,@Entity标签代表的是java代码的类名
比如
我们最初进行uml设计的时候命名的是mysql中的表的名字,后面hibernate tools生成po名字的时候我们都没有进行人工干预,现在PO生成了以后,你让我手动把所有java代码里的类名由自动生成的PO名,手动改成与uml中一样的数据库名字?这个工作量太大,而且太不科学。
参考蒋锋的POM.xml文件,修改hibernate对应依赖的版本,试试看。修改后hibernate相关的部分如下所示:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.2.3.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId>
<version>1.0.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.2.3.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>3.3.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.common</groupId>
<artifactId>hibernate-commons-annotations</artifactId>
<version>5.0.1.Final</version>
</dependency>
修改以后update projects
奇迹发生了如下图
不再报错,那么我们可以继续了。
回到文章的开头,我们为什么要在eclipse上通过jboss插件依据mysql的数据库的结构(数据表、字段等信息)生成java端的PO代码呢?答案是:为了保持数据库表和PO代码(关系型数据库与面向对象的类)之间的一致性。
我们回顾一下,在纯粹使用Spring(spring JDBC)编码的阶段,我们的DAO层代码文件中,注入(@Autowired)了JdbcTemplate类的实例去操纵的是手写的domain object。这些DO不需要采用@标注,他们只是很普通的POJO。我们也不需要在Spring配置文件中特别的去扫描他们。因为Spring JDBC的核心其实只是对Java JDBC的封装,它完成的工作只是执行SQL语句,将返回值保存入比如ArrayList这类java容器,容器中每一个元素都是DO,也就是我们自定义的java类。
从上述描述我们可以看到,单纯使用Spring JDBC访问数据库的时候,Domain层的POJO不需要被注册的。
但是我们改用Hibernate访问数据库以后,必须在Spring的配置文件中明确配置两个内容:
1.Hiberante
2.PO代码的文件夹(让Spring扫描这个文件夹)