本文旨在从一个空工程一步一步地配置Spring,空工程见上一篇文章创建Maven父子工程。
一、spring基本配置
1. 添加spring依赖
父工程pom.xml添加spring依赖
<dependencyManagement>
<dependencies>
<!-- 使用spring的BOM管理依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>4.2.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- spring配置开始 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- spring配置结束 -->
</dependencies>
2. web.xml添加spring配置
修改web.xml文件为如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:META-INF/dispatcher.xml</param-value>
</context-param>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>
3. 添加dispatcher.xml文件
在web工程的src/main/resources/META-INF目录下添加dispatcher.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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 加入下面这两个配置才能支持注解功能 -->
<bean id="handlerMapping" class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />
<bean id="handlerAdapter" class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />
<!-- 本工程拦截器 -->
<mvc:interceptors>
<bean class="org.springframework.web.servlet.mvc.WebContentInterceptor">
<property name="cacheSeconds" value="0" />
<property name="useExpiresHeader" value="true" />
<property name="useCacheControlHeader" value="true" />
<property name="useCacheControlNoStore" value="true" />
<property name="alwaysUseFullPath" value="true" />
</bean>
</mvc:interceptors>
<!-- 扫描包,如果用扫描则不用再写 <context:annotation-config /> -->
<context:component-scan base-package="com.alan.controller" />
<context:component-scan base-package="com.alan.service.impl" />
<context:component-scan base-package="com.alan.dao.impl" />
</beans>
4. 添加controller/service/dao等类
下面直接贴出各实现类。
(1)TestDaoImpl.java
package com.alan.dao.impl;
import org.springframework.stereotype.Repository;
import com.alan.dao.TestDao;
@Repository("testDao")
public class TestDaoImpl implements TestDao {
@Override
public String test() {
return "../index.html";
}
}
(2)TestServiceImpl.java
package com.alan.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.alan.dao.TestDao;
import com.alan.service.TestService;
@Service("testService")
public class TestServiceImpl implements TestService {
@Autowired
private TestDao testDao;
@Override
public String test() {
return testDao.test();
}
}
(3)TestController.java
package com.alan.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.alan.service.TestService;
@RestController
@RequestMapping("/alan")
public class TestController {
@Autowired
private TestService testService;
@RequestMapping("/test")
public String test(){
return testService.test();
}
}
5. 访问
重新编译工程并启动tomcat,通过浏览器访问,如果出现以下内容则表示配置正确。
注意:若tomcat启动老是报org.springframework.web.context.ContextLoaderListener找不到,把Eclipse关了,并把工作空间中Server/.metadata/RemoteTempFiles等几个目录删除,重新打开Eclipse并配置Preferences且重新导入工程再试。
(防采集:本文唯一网址在CSDN上,博主会不定期更新加载其它配置,其它均为未经博主同意转载的,原文为http://blog.csdn.net/tangtong1/article/details/51442757)
二、配置Json支持
6. 添加Json依赖
修改父工程的pom.xml,添加以下内容:
<!-- json配置开始 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.4</version>
</dependency>
<!-- json配置结束 -->
7. 配置@ResponseBody返回Json
在web工程的dispatcher.xml文件中添加如下配置:
<!-- 加入以下配置支持@ResponseBody返回Json格式 -->
<mvc:annotation-driven>
<mvc:message-converters>
<bean
class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>application/json;charset=UTF-8</value>
<value>text/html;charset=UTF-8</value>
<value>text/plain;charset=UTF-8</value>
</list>
</property>
<!-- 为null字段时是否显示 -->
<!-- <property name="features"> <list> <value>WriteMapNullValue</value>
<value>QuoteFieldNames</value> <value>WriteNullStringAsEmpty</value> </list>
</property> -->
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
8. 再次访问
重启tomcat,通过浏览器再次访问,如果返回以下内容表示配置@ResponseBody正确。
注:如果Controller类上注解为@RestController则相当于方法上注解了@ResponseBody。
9. 添加返回Model方法
新建Model,并添加对应的dao/service/controller方法返回此Model。
(1)TestModel.java
package com.alan.model;
public class TestModel {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
(2)TestDaoImpl.java
@Override
public TestModel testModel() {
TestModel testModel = new TestModel();
testModel.setId(1);
testModel.setName("tt");
return testModel;
}
(3)TestServiceImpl.java
@Override
public TestModel testModel() {
return testDao.testModel();
}
(4)TestController.java
@RequestMapping("/test-model")
public TestModel testModel(){
return testService.testModel();
}
10. 访问
重启tomcat,通过浏览器访问,如果返回以下内容则表示json配置正确。
(防采集:本文唯一网址在CSDN上,博主会不定期更新加载其它配置,其它均为未经博主同意转载的,原文为http://blog.csdn.net/tangtong1/article/details/51442757)
三、配置单元测试
11. 添加单元测试依赖
修改父工程pom.xml,添加单元测试依赖。
<!-- 单元测试开始 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- 单元测试结束 -->
12. 添加测试类
新建一个测试类。
package com.alan.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.alan.service.TestService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath*:META-INF/dispatcher.xml")
public class UnitTest {
@Autowired
private TestService testService;
@Test
public void test(){
String result = testService.test();
System.out.println(result);
}
}
13. 运行测试类
Run As –> JUnit Test,运行结果如下表示配置单元测试正确。
(防采集:本文唯一网址在CSDN上,博主会不定期更新加载其它配置,其它均为未经博主同意转载的,原文为http://blog.csdn.net/tangtong1/article/details/51442757)
四、配置日志管理
此处介绍两种日志管理工具,一个是log4j,一个是logback。
14. 添加log4j相关依赖
修改父工程的pom.xml,添加以下内容。
<!-- 日志配置开始 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.5.8</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.5.8</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.5.8</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</dependency>
<!-- 日志配置结束 -->
15. 添加log4j.properties
在web工程的src/main/resources目录下添加log4j.properties文件。
log4j.rootCategory=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %t %c{2}:%L - %m%n
log4j.category.org.springframework.beans.factory=DEBUG
修改log4j.rootCategory中的DEBUG为INFO/ERROR等即可输出相应级别的日志。
16. 测试log4j
修改单元测试类为如下。
package com.alan.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.alan.controller.TestController;
import com.alan.service.TestService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath*:META-INF/dispatcher.xml")
public class UnitTest {
private Logger logger = LoggerFactory.getLogger(UnitTest.class);
@Autowired
private TestService testService;
@Test
public void test(){
logger.debug("---------------------测试开始----------------------------");
String result = testService.test();
System.out.println(result);
logger.debug("---------------------测试结束----------------------------");
}
}
运行测试类,如果输出结果为以下内容则表示log4j配置正确。
17. 添加logback依赖
修改父工程的pom.xml,删除之前log4j的依赖,添加logback相关的依赖。
<!-- 日志配置开始 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.12</version>
</dependency>
<!-- logback配置开始 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>org.logback-extensions</groupId>
<artifactId>logback-ext-spring</artifactId>
<version>0.1.2</version>
</dependency>
<!-- logback配置结束 -->
<!-- 日志配置结束 -->
18. 添加logback.xml文件
在web工程的src/main/resources目录下添加logback.xml文件,官网说会先检测classpath下的logback.groovy文件,如果没有再寻找logback-test.xml文件,最后才会找logback.xml文件,不过我们一般就直接使用logback.xml文件啦。
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="log.base" value="E:/workspace/self/log/alan-springmvc" />
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36}:%L -%msg%n</pattern>
</encoder>
</appender>
<appender name="logfile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<Encoding>UTF-8</Encoding>
<File>${log.base}/alan-springmvc.log</File>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36}:%L -%msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.base}/alan-springmvc-%d{yyyyMMdd}-%i.log</fileNamePattern>
<MaxHistory>100</MaxHistory>
<TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<MaxFileSize>100MB</MaxFileSize>
</TimeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
</appender>
<root level="debug">
<appender-ref ref="stdout" />
<appender-ref ref="logfile" />
</root>
</configuration>
修改root元素的level属性为debug/info/error等即可输出相应级别的日志。
19. 添加logback监听
修改web工程的web.xml,添加以下内容。
<!-- logback监听配置 -->
<context-param>
<param-name>logbackConfigLocation</param-name>
<param-value>classpath:logback.xml</param-value>
</context-param>
<listener>
<listener-class>ch.qos.logback.ext.spring.web.LogbackConfigListener</listener-class>
</listener>
20. 测试logback
运行之前的单元测试类,如果输出以下内容则表示logback配置正确。
注:从log4j转到logback之前类中的写法完全不用改变,这是slf4j的威力。
打开日志文件(logback.xml中配置的第二个appender),如果看到以下内容表示logback配置正确。
注:../index.html这行是system.out.println输出的,所以不在日志文件中。
(防采集:本文唯一网址在CSDN上,博主会不定期更新加载其它配置,其它均为未经博主同意转载的,原文为http://blog.csdn.net/tangtong1/article/details/51442757)
五、集成mybatis
本文采用bonecp管理数据库连接,数据库采用mysql。
21. 添加bonecp/mybatis/mysql/jdbc依赖
其中,mysql的依赖版本最好跟数据库保持一致,否则可能会出现连不上的情况。
<!-- 连接池配置开始 -->
<dependency>
<groupId>com.jolbox</groupId>
<artifactId>bonecp</artifactId>
<version>0.8.0.RELEASE</version>
</dependency>
<!-- 连接池配置结束 -->
<!-- mybatis配置开始 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- mybatis配置结束 -->
<!--mysql配置开始 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
<!--mysql配置结束 -->
<!-- jdbc配置结束 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<!-- jdbc配置结束 -->
22. 添加spring-mybatis.xml
在web工程的src/main/resources/META-INF目录下增加配置文件spring-mybatis.xml。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- 配置DataSource数据源 -->
<bean id="dataSource" class="com.jolbox.bonecp.BoneCPDataSource"
destroy-method="close">
<!-- 数据库驱动 -->
<property name="driverClass" value="${jdbc_driverClassName}" />
<!-- 相应驱动的jdbcUrl -->
<property name="jdbcUrl" value="${jdbc_url}" />
<!-- 数据库的用户名 -->
<property name="username" value="${jdbc_username}" />
<!-- 数据库的密码 -->
<property name="password" value="${jdbc_password}" />
<!-- /** Maximum age of an unused connection before it is closed off. */ -->
<!-- 设置分区个数。这个参数默认为1,建议3-4(根据特定应用程序而定)。 为了减少锁竞争和改善性能,从当前线程分区(thread-affinity)中获取一个connection,
也就是这个样子:partitions[Thread.currentThread().getId() % partitionCount]。 当拥有充足的短期(short-lived)的线程时候,这个参数设置越大,性能越好。
当超过一定的阀值时,连接池的维护工作就可能对性能造成一定的负面影响(仅当分区上的connection使用耗尽时) -->
<property name="partitionCount" value="${db.partitionCount}" />
<!-- 设置每个分区含有connection最大个数。这个参数默认为2。如果小于2,BoneCP将设置为50。 比如:partitionCount设置为3,maxConnectionPerPartition设置为5,你就会拥有总共15个connection。
注意:BoneCP不会将这些connection一起创建出来,而是说在需要更多connection的时候从minConnectionsPerPartition参数开始逐步地增长connection数量。 -->
<property name="maxConnectionsPerPartition" value="${db.maxConnectionsPerPartition}" />
<!-- 设置每个分区含有connection最大小个数。这个参数默认为0。 -->
<property name="minConnectionsPerPartition" value="${db.minConnectionsPerPartition}" />
<!-- 设置分区中的connection增长数量。这个参数默认为1。 当每个分区中的connection大约快用完时,BoneCP动态批量创建connection,
这个属性控制一起创建多少个connection(不会大于maxConnectionsPerPartition)。 注意:这个配置属于每个分区的设置。 -->
<property name="acquireIncrement" value="${db.acquireIncrement}" />
<!-- 设置连接池阀值。这个参数默认为20。如果小于0或是大于100,BoneCP将设置为20。 连接池观察线程(PoolWatchThread)试图为每个分区维护一定数量的可用connection。
这个数量趋于maxConnectionPerPartition和minConnectionPerPartition之间。这个参数是以百分比的形式来计算的。
例如:设置为20,下面的条件如果成立:Free Connections / MaxConnections < poolAvailabilityThreshold;就会创建出新的connection。
换句话来说连接池为每个分区至少维持20%数量的可用connection。 设置为0时,每当需要connection的时候,连接池就要重新创建新connection,这个时候可能导致应用程序可能会为了获得新connection而小等一会。 -->
<property name="poolAvailabilityThreshold" value="${db.poolAvailabilityThreshold}" />
<!-- 设置获取connection超时的时间。这个参数默认为Long.MAX_VALUE;单位:毫秒。 在调用getConnection获取connection时,获取时间超过了这个参数,就视为超时并报异常。 -->
<property name="connectionTimeoutInMs" value="${db.connectionTimeoutInMs}" />
<!-- /** A connection older than maxConnectionAge will be destroyed and
purged from the pool. */ -->
<!-- 设置connection的存活时间。这个参数默认为0,单位:毫秒。设置为0该功能失效。 通过ConnectionMaxAgeThread观察每个分区中的connection,不管connection是否空闲,
如果这个connection距离创建的时间大于这个参数就会被清除。当前正在使用的connection不受影响,直到返回到连接池再做处理。 -->
<!-- 48小时关闭一个链接 -->
<property name="maxConnectionAgeInSeconds" value="${db.maxConnectionAgeInSeconds}" />
<!-- /** SQL statement to use for keep-alive/test of connection. */ -->
<property name="connectionTestStatement" value="${db.connectionTestStatement}" />
<!-- 设置connection的空闲存活时间。这个参数默认为60,单位:分钟。设置为0该功能失效。 通过ConnectionTesterThread观察每个分区中的connection,如果这个connection距离最后使用的时间大于这个参数就会被清除。
注意:这个参数仅和idleConnectionTestPeriodInSeconds搭配使用,而且不要在这里设置任何挑衅的参数! -->
<!-- 1小时回收空闲链接 -->
<property name="idleMaxAgeInMinutes" value="${db.idleMaxAgeInMinutes}" />
<!-- /** Connections older than this are sent a keep-alive statement. */ -->
<!-- 设置测试connection的间隔时间。这个参数默认为240*60,单位:分钟。设置为0该功能失效。 通过ConnectionTesterThread观察每个分区中的connection,
如果这个connection距离最后使用的时间大于这个参数并且距离上一次测试的时间大于这个参数就会向数据库发送一条测试语句,如果执行失败则将这个connection清除。
注意:这个值仅和idleMaxAge搭配使用,而且不要在这里设置任何挑衅的参数! -->
<!-- 4小时检测一次空闲链接 -->
<property name="idleConnectionTestPeriodInMinutes" value="${db.idleConnectionTestPeriodInMinutes}" />
<!-- /** After attempting to acquire a connection and failing, try to connect
these many times before giving up. */ -->
<!-- 设置重新获取连接的次数。这个参数默认为5。 获取某个connection失败之后会多次尝试重新连接,如果在这几次还是失败则放弃。 -->
<property name="acquireRetryAttempts" value="${db.acquireRetryAttempts}" />
<!-- 设置重新获取连接的次数间隔时间。这个参数默认为7000,单位:毫秒。如果小于等于0,BoneCP将设置为1000。 获取connection失败之后再次尝试获取connection的间隔时间。 -->
<property name="acquireRetryDelayInMs" value="${db.acquireRetryDelayInMs}" />
<!-- 设置连接池初始化功能。这个参数默认为false。 设置为true,连接池将会初始化为空,直到获取第一个connection。 -->
<property name="lazyInit" value="${db.lazyInit}" />
<!-- 设置是否关闭JMX功能。这个参数默认为false。 -->
<property name="disableJMX" value="${db.disableJMX}" />
<!-- 设置连接池名字。用于当作JMX和助手线程名字的后缀。 -->
<property name="poolName" value="${db.poolName}" />
<!-- /** Min no of prepared statements to cache. */ -->
<!-- 设置statement缓存个数。这个参数默认为0。 -->
<property name="statementsCacheSize" value="${db.statementsCacheSize}" />
<!-- 设置是否开启记录SQL语句功能。这个参数默认是false。 将执行的SQL记录到日志里面(包括参数值)。 -->
<property name="logStatementsEnabled" value="${db.logStatementsEnabled}" />
<!-- 设置执行SQL的超时时间。这个参数默认为0;单位:毫秒。 当查询语句执行的时间超过这个参数,执行的情况就会被记录到日志中。 设置为0时,该功能失效。 -->
<property name="queryExecuteTimeLimitInMs" value="${db.queryExecuteTimeLimit}" />
</bean>
<!-- 对数据源进行事务管理 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- 配置SqlSessionFactoryBean -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- mapper和resultmap配置路径 -->
<property name="mapperLocations">
<list>
<value>classpath:mapper/*Mapper.xml</value>
</list>
</property>
</bean>
<!-- 通过扫描的模式 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.alan.mapper" />
</bean>
</beans>
23. 修改dispatcher.xml
(1)增加对属性文件读取的支持;
<!-- 本工程属性配置文件 -->
<bean id="configProperties"
class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="locations">
<list>
<value>file:/etc/*.properties</value>
<value>file:/E:/workspace/self/etc/*.properties</value>
</list>
</property>
<property name="fileEncoding" value="utf-8" />
</bean>
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer">
<property name="properties" ref="configProperties" />
</bean>
(2)引入spring-mybatis.xml文件
<import resource="classpath:META-INF/spring-mybatis.xml" />
24. 添加jdbc.properties
在E:/workspace/self/etc目录下增加数据库连接配置文件jdbc.properties。
jdbc_driverClassName=com.mysql.jdbc.Driver
jdbc_url=jdbc:mysql://127.0.0.1:3306/fcuh_user?useUnicode=true&characterEncoding=UTF-8&characterSetResults=UTF-8&useOldAliasMetadataBehavior=true&noAccessToProcedureBodies=true
jdbc_username=admin
jdbc_password=1234
#Number of partitions.
db.partitionCount=3
#Min number of connections per partition.
db.minConnectionsPerPartition=2
#Max number of connections per partition.
db.maxConnectionsPerPartition=10
#Number of new connections to create in 1 batch.
db.acquireIncrement=2
#Time to wait before a call to getConnection() times out and returns an error.
db.connectionTimeoutInMs=30000
#Number of release-connection helper threads to create per partition.
#A connection older than maxConnectionAge will be destroyed and purged from the pool.
db.maxConnectionAgeInSeconds=172800
#SQL statement to use for keep-alive/test of connection.
db.connectionTestStatement=select 1 from dual
#Maximum age of an unused connection before it is closed off.
db.idleMaxAgeInMinutes=60
#Connections older than this are sent a keep-alive statement.
db.idleConnectionTestPeriodInMinutes=60
#After attempting to acquire a connection and failing, try to connect these many times before giving up.
db.acquireRetryAttempts=5
#After attempting to acquire a connection and failing, wait for this value before attempting to acquire a new connection again.
db.acquireRetryDelayInMs=5000
#If set to true, the connection pool will remain empty until the first connection is obtained.
db.lazyInit=false
#Set to true to disable JMX.
db.disableJMX=false
#Name of the pool for JMX and thread names.
db.poolName=logistics
#Min no of prepared statements to cache.
db.statementsCacheSize=50
#If set to true, log SQL statements being executed.
db.logStatementsEnabled=false
#Queries taking longer than this limit to execute are logged.
db.queryExecuteTimeLimit=0
#Create more connections when we hit x% of our possible number of connections.
db.poolAvailabilityThreshold=60
25. 修改dao/service/controller/mapper类
修改dao/service/controller类,在dao工程添加新的包com.alan.mapper,并添加新类TestMapper.java。
注:主要测试select/update/事务操作。
(1)TestMapper.java
package com.alan.mapper;
import java.util.List;
public interface TestMapper {
List<Object> testDB();
int testUpdate();
int testInsert();
}
(2)TestDaoImpl.java
package com.alan.dao.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.alan.dao.TestDao;
import com.alan.mapper.TestMapper;
import com.alan.model.TestModel;
@Repository("testDao")
public class TestDaoImpl implements TestDao {
@Autowired
private TestMapper testMapper;
@Override
public String test() {
return "../index.html";
}
@Override
public TestModel testModel() {
TestModel testModel = new TestModel();
testModel.setId(1);
testModel.setName("tt");
return testModel;
}
@Override
public List<Object> testDB() {
return testMapper.testDB();
}
@Override
public int testUpdate(){
return testMapper.testUpdate();
}
@Override
public int testInsert(){
return testMapper.testInsert();
}
}
(3)TestServiceImpl.java
注:testTx()方法会抛出异常以便测试事务。
package com.alan.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.alan.dao.TestDao;
import com.alan.model.TestModel;
import com.alan.service.TestService;
@Service("testService")
public class TestServiceImpl implements TestService {
@Autowired
private TestDao testDao;
@Override
public String test() {
return testDao.test();
}
@Override
public TestModel testModel() {
return testDao.testModel();
}
@Override
public List<Object> testDB() {
return testDao.testDB();
}
@Override
public int testUpdate(){
return testDao.testUpdate();
}
@Override
public int testInsert(){
return testDao.testInsert();
}
@Override
@Transactional(propagation=Propagation.REQUIRED,rollbackFor=Exception.class)
public void testTx(Integer nullInt){
testUpdate();
nullInt.intValue();
testInsert();
}
}
(4)TestController.java
package com.alan.controller;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.alan.model.TestModel;
import com.alan.service.TestService;
@RestController
@RequestMapping("/alan")
public class TestController {
private Logger logger = LoggerFactory.getLogger(TestController.class);
@Autowired
private TestService testService;
@RequestMapping("/test")
public String test(){
logger.debug("enter into test");
return testService.test();
}
@RequestMapping("/test-model")
public TestModel testModel(){
return testService.testModel();
}
@RequestMapping("/test-db")
public List<Object> testDB(){
return testService.testDB();
}
@RequestMapping("/test-update")
public int testUpdate(){
return testService.testUpdate();
}
@RequestMapping("/test-tx")
public String testTx(){
testService.testTx(null);
return "success";
}
}
26. 添加TestMapper.xml
在dao工程的src/main/resources/mapper目录下,添加TestMapper.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="com.alan.mapper.TestMapper" >
<select id="testDB" resultType="map">
select user_id, mobile from basic_user_info limit 10
</select>
<update id="testUpdate">
update basic_user_info
set mobile = '12345678911'
where user_id = 1
</update>
<insert id="testInsert">
insert into basic_user_info(user_id,mobile,status)
values(3,'123x456x789',1)
</insert>
</mapper>
27. 测试
前提:数据库建好了表,并插入了相关数据,至少包含3列:user_id,mobile,status。
(1)测试select
(2)测试update
(3)测试事务
浏览器报错,后台日志回滚,数据库未插入id为3的数据,说明事务测试成功。
至此,表示mybatis配置正确,并支持事务。
(防采集:本文唯一网址在CSDN上,博主会不定期更新加载其它配置,其它均为未经博主同意转载的,原文为http://blog.csdn.net/tangtong1/article/details/51442757)
六、编写通用类
我们知道项目中基本每个表都会有增删改查这些方法,要是针对每个表都要写这么一大堆重复的方法着实让人心烦,这章我们来写通用的类以提高开发的效率。
28. 新建common工程
为方便我们直接建在父工程下了,按实际项目经验,common工程应该是一个独立的工程。
切换到父工程,执行以下命令。
mvn archetype:generate -DarchetypeCatalog=internal -DgroupId=com.alan.springmvc.common -DartifactId=alan-springmvc-common -DarchetypeArtifactId=maven-archetype-quickstart -Dversion=1.0
同样,删除pom.xml文件中的groupId和versiong两行,删除自动生成的Junit依赖。
29. 新建各通用类
以下类都放在common工程相应的包下。
(1)BaseMapper.java
package com.alan.parent.mapper;
import java.util.List;
public interface BaseMapper<T> {
T queryById(int id);
List<T> queryAll();
int add(T t);
int update(T t);
int delete(int id);
}
(2)BaseSqlDao.java
注意:此接口是包访问权限。
package com.alan.parent.dao;
import java.util.List;
import java.util.Map;
interface BaseSqlDao {
<V> V queryVoByParam(Map<String, Object> param);
<V> List<V> queryListByParam(Map<String, Object> param);
void multipleDML(Map<String, Object> param);
}
(3)BaseSqlDaoImpl.java
注意:此类直接调用mybatis的API操作*Mapper.xml文件,跳过了*Mapper.java,具体使用方法后续介绍。其中,multipleDML方法暂不实现,有兴趣的可以参考另一篇博客spring+mybatis一个方法执行多条更新语句,实现批量DML。
package com.alan.parent.dao;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
class BaseSqlDaoImpl implements BaseSqlDao {
@Resource
private SqlSessionFactory sqlSessionFactory;
private String baseMapperPath;
public String getBaseMapperPath() {
return baseMapperPath;
}
public void setBaseMapperPath(String baseMapperPath) {
this.baseMapperPath = baseMapperPath;
}
@Override
public <V> V queryVoByParam(Map<String, Object> param) {
SqlSession session = sqlSessionFactory.openSession();
try {
return session.selectOne(baseMapperPath + "." + param.get("mapperName") + "." + param.get("selectId"),
param);
} finally {
session.close();
}
}
@Override
public <V> List<V> queryListByParam(Map<String, Object> param) {
SqlSession session = sqlSessionFactory.openSession();
try {
return session.selectList(baseMapperPath + "." + param.get("mapperName") + "." + param.get("selectId"),
param);
} finally {
session.close();
}
}
@Override
public void multipleDML(Map<String, Object> param) {
}
}
(4)BaseDao.java
package com.alan.parent.dao;
import java.util.List;
import java.util.Map;
public interface BaseDao<T> {
T queryById(int id);
List<T> queryAll();
int add(T t);
int update(T t);
int delete(int id);
<V> V queryVoByParam(Map<String, Object> param);
<V> List<V> queryListByParam(Map<String, Object> param);
void multipleDML(Map<String, Object> param);
}
(5)BaseDaoImpl.java
注意:此类中注入了BaseSqlDao实例。
package com.alan.parent.dao;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import com.alan.parent.mapper.BaseMapper;
public abstract class BaseDaoImpl<T> implements BaseDao<T> {
@Resource
private BaseSqlDao baseSqlDao;
private BaseMapper<T> mapper;
@PostConstruct
public void initMapper() {
this.mapper = init();
}
public abstract BaseMapper<T> init();
@Override
public T queryById(int id) {
return mapper.queryById(id);
}
@Override
public List<T> queryAll() {
return mapper.queryAll();
}
@Override
public int add(T t) {
return mapper.add(t);
}
@Override
public int update(T t) {
return mapper.update(t);
}
@Override
public int delete(int id) {
return mapper.delete(id);
}
@Override
public <V> V queryVoByParam(Map<String, Object> param){
return baseSqlDao.queryVoByParam(param);
}
@Override
public <V> List<V> queryListByParam(Map<String, Object> param){
return baseSqlDao.queryListByParam(param);
}
@Override
public void multipleDML(Map<String, Object> param){
baseSqlDao.multipleDML(param);
}
}
(6)BaseService.java
package com.alan.parent.service;
import java.util.List;
import java.util.Map;
public interface BaseService<T> {
T queryById(int id);
List<T> queryAll();
int add(T t);
int update(T t);
int delete(int id);
<V> V queryVoByParam(Map<String, Object> param);
<V> List<V> queryListByParam(Map<String, Object> param);
void multipleDML(Map<String, Object> param);
}
(7)BaseServiceImpl.java
package com.alan.parent.service;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import com.alan.parent.dao.BaseDao;
public abstract class BaseServiceImpl<T> implements BaseService<T> {
private BaseDao<T> dao;
@PostConstruct
public void initDao(){
this.dao = init();
}
public abstract BaseDao<T> init();
@Override
public T queryById(int id) {
return dao.queryById(id);
}
@Override
public List<T> queryAll() {
return dao.queryAll();
}
@Override
public int add(T t) {
return dao.add(t);
}
@Override
public int update(T t) {
return dao.update(t);
}
@Override
public int delete(int id) {
return dao.delete(id);
}
@Override
public <V> V queryVoByParam(Map<String, Object> param) {
return dao.queryVoByParam(param);
}
@Override
public <V> List<V> queryListByParam(Map<String, Object> param) {
return dao.queryListByParam(param);
}
@Override
public void multipleDML(Map<String, Object> param) {
dao.multipleDML(param);
}
}
(8)BaseController.java
说明:
- 前五个方法需要在对应的*Mapper.xml中定义相应的语句,比如queryById,需要定义一条select其id为queryById;
- 之后的两个方法的注解会从url中获取selectId,这个即*Mapper.xml中select的id;
- 后面三条不带@RequestMapping的用于自定义查询。
以上方法的使用会在后续介绍。
package com.alan.parent.controller;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import com.alan.parent.service.BaseService;
public abstract class BaseController<T> {
private Logger logger = LoggerFactory.getLogger(BaseController.class);
private BaseService<T> service;
@PostConstruct
public void initService() {
this.service = init();
}
public abstract BaseService<T> init();
@RequestMapping("/queryById")
public T queryById(int id) {
return service.queryById(id);
}
@RequestMapping("/queryAll")
public List<T> queryAll() {
return service.queryAll();
}
@RequestMapping("/add")
public int add(T t) {
return service.add(t);
}
@RequestMapping("/update")
public int update(T t) {
return service.update(t);
}
@RequestMapping("/delete")
public int delete(int id) {
return service.delete(id);
}
/**
* 从url上获取selectId并查询单条数据,其中selectId与*Mapper.xml中select的id对应
* @param request
* @param selectId
* @return
*/
@RequestMapping("/queryVo/{selectId}")
public Object queryVo(HttpServletRequest request, @PathVariable String selectId) {
Map<String, Object> param = buildParam(request);
param.put("selectId", selectId);
param.put("mapperName", getMapperName());
return queryVoByParam(param);
}
/**
* 从url上获取selectId并查询多条数据,其中selectId与*Mapper.xml中select的id对应
* @param request
* @param selectId
* @return
*/
@RequestMapping("/queryList/{selectId}")
public List<Object> queryList(HttpServletRequest request, @PathVariable String selectId) {
Map<String, Object> param = buildParam(request);
param.put("selectId", selectId);
param.put("mapperName", getMapperName());
return queryListByParam(param);
}
public <V> V queryVoByParam(Map<String, Object> param) {
return service.queryVoByParam(param);
}
public <V> List<V> queryListByParam(Map<String, Object> param) {
return service.queryListByParam(param);
}
public void multipleDML(Map<String, Object> param) {
service.multipleDML(param);
}
/**
* 从request中构建出参数
* @param request
* @return
*/
private Map<String, Object> buildParam(HttpServletRequest request) {
Map<String, Object> param = new HashMap<>();
Map<String, String[]> paramMap = request.getParameterMap();
for (Entry<String, String[]> e : paramMap.entrySet()) {
if (e.getValue() == null || e.getValue().toString() == null || e.getValue().toString().length() == 0) {
continue;
}
try {
param.put(e.getKey(), URLDecoder.decode(e.getValue()[0], "utf-8"));
} catch (UnsupportedEncodingException e1) {
logger.error("decode error, the value is : " + e.getValue()[0]);
}
}
return param;
}
/**
* 根据泛型类型获取Mapper的名字
* @return
*/
private String getMapperName() {
Type genType = this.getClass().getGenericSuperclass();
String paramType = ((ParameterizedType) genType).getActualTypeArguments()[0].getTypeName();
String mapperName = paramType.substring(paramType.lastIndexOf('.') + 1) + "Mapper";
return mapperName;
}
}
30. 新建spring-common.xml配置文件
在common工程的src/main/resource/META-INFO目录下新建spring-common.xml文件,用于配置baseSqlDao。
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="baseSqlDao" class="com.alan.parent.dao.BaseSqlDaoImpl">
<property name="baseMapperPath" value="com.alan.mapper"></property>
</bean>
</beans>
31. 引入spring-common.xml配置文件
在web工程的dispatcher.xml文件中引入上述配置文件。
<import resource="classpath:META-INF/spring-common.xml" />
32. 新建表
数据库中新建user_info及user_address表,并随意插入几条数据,用于测试上述通用类。
create table user_info
(
id int,
name varchar(50),
mobile varchar(11)
);
create table user_address
(
id int primary key auto_increment,
user_id int,
address varchar(100)
)
33. 新建user_info表对应的各类
(1)实体类
- UserInfo.java
在dao工程下新建包com.alan.entity。
package com.alan.entity;
public class UserInfo {
private Integer id;
private String name;
private String mobile;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
}
- UserAddress.java
package com.alan.entity;
public class UserAddress {
private Integer id;
private Integer userId;
private String address;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
- UserAddressModel.java
在dao工程下新建包com.alan.model,并新建UserAddressModel,其中包含address的列表。
package com.alan.model;
import java.util.List;
import com.alan.entity.UserAddress;
public class UserAddressModel {
private Integer id;
private String name;
private List<UserAddress> addresses;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<UserAddress> getAddresses() {
return addresses;
}
public void setAddresses(List<UserAddress> addresses) {
this.addresses = addresses;
}
}
(2)UserInfoMapper.java
直接继承BaseMapper接口。
package com.alan.mapper;
import com.alan.entity.UserInfo;
import com.alan.parent.mapper.BaseMapper;
public interface UserInfoMapper extends BaseMapper<UserInfo>{
}
(3)UserInfoDao.java
直接继承BaseDao接口。
package com.alan.dao;
import com.alan.entity.UserInfo;
import com.alan.parent.dao.BaseDao;
public interface UserInfoDao extends BaseDao<UserInfo>{
}
(4)UserInfoDaoImpl.java
继承BaseDaoImpl并实现UserInfoDao接口,注入userInfoMapper,并覆写init方法,传入userInfoMapper,这样就可以直接使用BaseDaoImpl中的方法了。
package com.alan.dao.impl;
import javax.annotation.Resource;
import org.springframework.stereotype.Repository;
import com.alan.dao.UserInfoDao;
import com.alan.entity.UserInfo;
import com.alan.mapper.UserInfoMapper;
import com.alan.parent.dao.BaseDaoImpl;
import com.alan.parent.mapper.BaseMapper;
@Repository("userInfoDao")
public class UserInfoDaoImpl extends BaseDaoImpl<UserInfo> implements UserInfoDao{
@Resource
private UserInfoMapper userInfoMapper;
@Override
public BaseMapper<UserInfo> init() {
return userInfoMapper;
}
}
(5)UserInfoService.java
package com.alan.service;
import com.alan.entity.UserInfo;
import com.alan.parent.service.BaseService;
public interface UserInfoService extends BaseService<UserInfo> {
}
(6)UserInfoServiceImpl.java
package com.alan.service.impl;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import com.alan.dao.UserInfoDao;
import com.alan.entity.UserInfo;
import com.alan.parent.dao.BaseDao;
import com.alan.parent.service.BaseServiceImpl;
import com.alan.service.UserInfoService;
@Service("userInfoService")
public class UserInfoServiceImpl extends BaseServiceImpl<UserInfo> implements UserInfoService {
@Resource
private UserInfoDao userInfoDao;
@Override
public BaseDao<UserInfo> init() {
return userInfoDao;
}
}
(7)UserInfoController.java
继承自BaseController,并初始化userInfoService,并新增加了三个方法queryUserInfoByMobile、queryUserInfoByName和queryRandomUser(假设手机号不重复,名字可重复),分别调用父类的queryVoByParam和queryListByParam方法,使用这两个方法,直接service/dao/mapper里的方法都不用写了,直接操作mapper.xml文件,非常之方便,当然,如果有点逻辑还是放在service里比较清晰一点。
package com.alan.controller;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.alan.entity.UserInfo;
import com.alan.parent.controller.BaseController;
import com.alan.parent.service.BaseService;
import com.alan.service.UserInfoService;
@RestController
@RequestMapping("/userInfo")
public class UserInfoController extends BaseController<UserInfo> {
@Resource
private UserInfoService userInfoService;
@Override
public BaseService<UserInfo> init() {
return userInfoService;
}
@RequestMapping("/queryUserInfoByMobile")
public UserInfo queryUserInfoByMobile(String mobile){
Map<String, Object> param = new HashMap<>();
param.put("mobile", mobile);
param.put("mapperName","UserInfoMapper");
param.put("selectId", "queryUserInfoByMobile");
return queryVoByParam(param);
}
@RequestMapping("/queryUserInfoByName")
public List<UserInfo> queryUserInfoByName(String name){
Map<String, Object> param = new HashMap<>();
param.put("name", name);
param.put("mapperName","UserInfoMapper");
param.put("selectId", "queryUserInfoByName");
return queryListByParam(param);
}
@RequestMapping("/queryRandomUser")
public Map<String, Object> queryRandomUser(){
Map<String, Object> param = new HashMap<>();
param.put("mapperName","UserInfoMapper");
param.put("selectId", "queryRandomUser");
return queryVoByParam(param);
}
}
34. 新建UserInfoMapper.xml文件
在dao工程的src/main/resources/mapper目录中新建UserInfoMapper.xml文件。
说明:
- 前面五个方法分别与BaseController中的方法对应(实际是BaseMapper);
- 之后的三个方法分别与UserInfoController中自定义的方法对应;
- 最后两个方法完全不在UserInfoController中,也不在其父类中,怎么调用呢?后续介绍-_-
<?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="com.alan.mapper.UserInfoMapper">
<sql id="columns">
id, name, mobile
</sql>
<sql id="columns-place-holder">
#{id}, #{name}, #{mobile}
</sql>
<resultMap type="com.alan.entity.UserInfo" id="userInfoEntity">
<id column="id" property="id" />
<result column="name" property="name" />
<result column="mobile" property="mobile" />
</resultMap>
<select id="queryById" resultMap="userInfoEntity ">
select <include refid="columns"></include>
from user_info
where id=#{id}
</select>
<select id="queryAll" resultMap="userInfoEntity">
select <include refid="columns"></include>
from user_info
</select>
<insert id="add">
insert into user_info (<include refid="columns"></include>)
values(<include refid="columns-place-holder"></include>)
</insert>
<update id="update" parameterType="com.alan.entity.UserInfo">
update user_info
set id = #{id}
<if test="name != null">
,name = #{name}
</if>
<if test="mobile != null">
,mobile = #{mobile}
</if>
where id = #{id}
</update>
<delete id="delete">
delete from user_info where id = #{id}
</delete>
<select id="queryUserInfoByMobile" resultMap="userInfoEntity">
select <include refid="columns"></include>
from user_info
where mobile = #{mobile}
</select>
<select id="queryUserInfoByName" resultMap="userInfoEntity">
select <include refid="columns"></include>
from user_info
where name = #{name}
</select>
<select id="queryRandomUser" resultType="map">
select <include refid="columns"></include>
from user_info
limit 1;
</select>
<select id="queryUserAddressByUserId" resultType="map">
select ui.id, ui.name, ua.address
from user_info ui, user_address ua
where ui.id = ua.user_id
and ui.id = #{userId}
</select>
<resultMap type="com.alan.entity.UserAddress" id="userAddressResult">
<result column="address" property="address"/>
</resultMap>
<resultMap type="com.alan.model.UserAddressModel" id="userAddressModelResult">
<id column="id" property="id"/>
<result column="name" property="name"/>
<collection property="addresses" resultMap="userAddressResult"></collection>
</resultMap>
<select id="queryUserAddressByUserIdReturnModel" resultMap="userAddressModelResult">
select ui.id, ui.name, ua.address
from user_info ui
left join user_address ua on ui.id = ua.user_id
where ui.id = #{userId}
</select>
</mapper>
35. 测试
启动tomcat,浏览器中测试各方法的执行。
(1)queryById
(2)queryAll
(3)add
(4)update
(5)delete
(6)queryUserInfoByMobile
(7)queryUserInfoByName
(8)queryRandomUser
主要用于验证不返回model,直接返回map类型。
(9)queryUserAddressByUserId
通过/queryList/{selectId}访问,返回的是map对象。
(10)queryUserAddressByUserIdReturnModel
通过/queryList/{selectId}访问,返回的是具体的model,注意与上例比较。
(11)queryUserAddressByUserIdReturnModel
通过/queryVo/{selectId}访问,返回的是具体的model,注意与上例比较。
可见,以上各方法都可正确执行,表示我们的通用类编写完美。
36. 通用类使用总结
(1)对于简单的增册改查,直接调用queryById、queryAll、add、update、delete方法即可;
(2)对于查询后就直接返回的,强烈推荐使用queryVo和queryList这两个方法,这种方式只需要在xml中定义好查询语句;
(3)如果还有业务逻辑需要处理,推荐调用父类的queryVoByParam和queryListByParam这两个方法,查询前后可处理业务逻辑,这种方式可以省去好几个方法的编写;
(4)此处只定义了查询的通用方法,像insert、update、delete也可以定义通用方法,与queryVoByParam和queryListByParam的定义类似,请读者自行参考mybatis源码定义。
37.补充
补充insert、update、delete的通用方法,这里只写BaseSqlDaoImpl和BaseController两个类。
(1)BaseSqlDaoImpl.java
@Override
public int updateByParam(Map<String, Object> param){
SqlSession session = sqlSessionFactory.openSession();
try {
return session.update(baseMapperPath + "." + param.get("mapperName") + "." + param.get("updateId"), param);
} finally {
session.close();
}
}
@Override
public int deleteByParam(Map<String, Object> param){
SqlSession session = sqlSessionFactory.openSession();
try {
return session.delete(baseMapperPath + "." + param.get("mapperName") + "." + param.get("deleteId"), param);
} finally {
session.close();
}
}
@Override
public int insertByParam(Map<String, Object> param){
SqlSession session = sqlSessionFactory.openSession();
try {
return session.insert(baseMapperPath + "." + param.get("mapperName") + "." + param.get("insertId"), param);
} finally {
session.close();
}
}
(2)BaseController.java
/**
* 从url上获取updateId并更新数据,其中updateId与*Mapper.xml中update的id对应
* @param request
* @param updateId
* @return
*/
@RequestMapping("/updateCommon/{updateId}")
public int updateCommon(HttpServletRequest request, @PathVariable String updateId) {
Map<String, Object> param = buildParam(request);
param.put("updateId", updateId);
param.put("mapperName", getMapperName());
return service.updateByParam(param);
}
/**
* 从url上获取deleteId并更新数据,其中deleteId与*Mapper.xml中delete的id对应
* @param request
* @param deleteId
* @return
*/
@RequestMapping("/deleteCommon/{deleteId}")
public int deleteCommon(HttpServletRequest request, @PathVariable String deleteId) {
Map<String, Object> param = buildParam(request);
param.put("deleteId", deleteId);
param.put("mapperName", getMapperName());
return service.deleteByParam(param);
}
/**
* 从url上获取insertId并更新数据,其中insertId与*Mapper.xml中insert的id对应
* @param request
* @param insertId
* @return
*/
@RequestMapping("/insertCommon/{insertId}")
public int insertCommon(HttpServletRequest request, @PathVariable String insertId) {
Map<String, Object> param = buildParam(request);
param.put("insertId", insertId);
param.put("mapperName", getMapperName());
return service.insertByParam(param);
}
(防采集:本文唯一网址在CSDN上,博主会不定期更新加载其它配置,其它均为未经博主同意转载的,原文为http://blog.csdn.net/tangtong1/article/details/51442757)
七、自动生成代码
从上述编写过程中可见,user_info这一个表,就要写实体类/mapper/dao/service/controller等这么多个类(包括mapper.xml),而且每个类中内容的形式基本保持一致,除了userInfo标识不同。那么,有没有方法不用写这些类呢, 答案是肯定的,下面我们就自己编写一个简单的代码生成工具。
38. 生成代码思路
(1)拆分类并定义model
一个类可以拆分成以下几部分:全路径、访问修饰符、其它修饰符(static/final等)、父类、接口、注解、导入信息、字段、方法等,当然,还有内部类什么乱七八糟的这就不考虑了。如果我们把这些信息都定义好了,那么生成一个类还不是手到擒来。然后,方法、字段等又能分成一个一个的Model。
(2)重写上述Model的toString()方法
这样让Model类自己把生成的代码写好,方便后续生成。
(3)定义生成代码的类
比如,给个表名生成相应的dao/service等代码,给出表的列信息生成相应的entity/mapper等代码。
(4)定义数据库帮助类
因为要根据数据库中的表生成相应的entity/mapper等,所以需要从数据字典中获取这些信息,自然要连数据库。
以上两步结合就可以生成所有的代码了,但是还不够完美,要是能自己选择生成哪些表对应的代码,岂不更完美。
(5)编写界面类
没错,通过swing组件,编写一个界面,从数据库中查询出表信息,自己选择需要生成哪些表对应的代码,自己选择需要生成哪些层的代码。
(6)后悔药
如果生成错了咋办,还要一个一个文件把这些生成的文件删除掉,太麻烦了,“后悔药”即为生成代码后,可以快速把刚生成的删除。
39. 成果展示
(1)操作界面
(2)生成的代码目录
(3)生成的代码
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="com.alan.mapper.UserTestXyzMapper">
<sql id="columns">
test_id, test_name, mobile, address
</sql>
<sql id="columns-place-holder">
#{testId}, #{testName}, #{mobile}, #{address}
</sql>
<resultMap type="com.alan.entity.UserTestXyz" id="userTestXyzEntity">
<id column="test_id" property="testId" />
<result column="test_name" property="testName" />
<result column="mobile" property="mobile" />
<result column="address" property="address" />
</resultMap>
<select id="queryById" resultMap="userTestXyzEntity ">
select <include refid="columns"></include>
from user_test_xyz
where test_id=#{testId}
</select>
<select id="queryAll" resultMap="userTestXyzEntity">
select <include refid="columns"></include>
from user_test_xyz
</select>
<insert id="add">
insert into user_test_xyz (<include refid="columns"></include>)
values(<include refid="columns-place-holder"></include>)
</insert>
<update id="update" parameterType="com.alan.entity.UserTestXyz">
update user_test_xyz
set test_id = #{testId}
<if test="testName != null">
,test_name = #{testName}
</if>
<if test="mobile != null">
,mobile = #{mobile}
</if>
<if test="address != null">
,address = #{address}
</if>
where test_id = #{testId}
</update>
<delete id="delete">
delete from user_test_xyz where test_id = #{testId}
</delete>
</mapper>
Controller.java
java文件有个弊端,import不会自动排好,Eclipse中ctrl+shift+o不知道怎么实现的。
package com.alan.controller;
import com.alan.parent.service.BaseService;
import com.alan.entity.UserTestXyz;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import com.alan.service.UserTestXyzService;
import org.springframework.web.bind.annotation.RestController;
import com.alan.parent.controller.BaseController;
@RestController
@RequestMapping("/userTestXyz")
public class UserTestXyzController extends BaseController<UserTestXyz> {
@Resource
private UserTestXyzService userTestXyzService;
@Override
public BaseService<UserTestXyz> init() {
return userTestXyzService;
}
}
40. 代码逻辑
注:此处是用纯java写的,后面博主采用了freemarker模板来写,事半功倍,有兴趣的同学可以去学习一下。
(1)Model类
在common工程下定义包com.alan.generate.model,并把所有生成代码相关model都置于其下。
(请注意model的set方法,及list的set改成了add)
ClassModel.java
package com.alan.generate.model;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.alan.generate.enums.ClassOrInterface;
import com.alan.generate.enums.Modifier;
import com.alan.generate.enums.Qualifier;
import com.alan.generate.utils.Utils;
public class ClassModel {
private String name;
private String simpleName;
private Class<?> genericType;
private Qualifier qualifier;
private List<Modifier> modifiers;
private ClassOrInterface classOrInterface;
private List<AnnotationModel> annotations;
private ClassTypeModel baseClass;
private List<ClassTypeModel> interfaces;
private List<FieldModel> fields;
private List<MethodModel> methods;
private Set<String> limitcs;
@Override
public String toString() {
String result = "#{package}#{limitcs}#{annotations}#{qualifier}#{modifier}#{classType}#{genericType} #{simpleName}#{baseClass}#{interface} {\n\n#{fields}#{methods}}";
if (Utils.isNotEmpty(name))
result = result.replace("#{package}", "package " + name.substring(0, name.lastIndexOf(".")) + ";\n\n");
if (Utils.isNotEmpty(limitcs)) {
final StringBuilder sb = new StringBuilder();
limitcs.forEach(l -> {
if (!name.substring(0, name.lastIndexOf(".")).equals(l.substring(0, l.lastIndexOf("."))) && !l.startsWith("java.lang."))
sb.append("import ").append(l).append(";\n");
});
result = result.replace("#{limitcs}", sb.toString() + "\n");
}
if (Utils.isNotEmpty(annotations)) {
final StringBuilder sb = new StringBuilder();
annotations.forEach(a -> sb.append(a.toString()).append("\n"));
result = result.replace("#{annotations}", sb.toString());
}
if (Utils.isNotEmpty(qualifier))
result = result.replace("#{qualifier}", qualifier.toString() + " ");
if (Utils.isNotEmpty(modifiers)) {
final StringBuilder sb = new StringBuilder();
modifiers.forEach(m -> sb.append(m.toString()).append(" "));
result = result.replace("#{modifier}", sb.toString());
}
result = result.replace("#{classType}", classOrInterface.toString());
if (Utils.isNotEmpty(genericType))
result = result.replace("#{genericType}", "<" + genericType.getSimpleName() + ">");
result = result.replace("#{simpleName}", simpleName);
if (Utils.isNotEmpty(baseClass))
result = result.replace("#{baseClass}", " extends " + baseClass.toString());
if (Utils.isNotEmpty(interfaces)) {
final StringBuilder sb = new StringBuilder();
interfaces.forEach(i -> sb.append(i.toString()).append(", "));
result = result.replace("#{interface}", " implements " + sb.substring(0, sb.lastIndexOf(",")));
}
if (Utils.isNotEmpty(fields)) {
StringBuilder sb = new StringBuilder();
fields.forEach(f -> sb.append(f.toString()).append("\n"));
result = result.replace("#{fields}", sb.toString() + "\n");
}
if (Utils.isNotEmpty(methods)) {
StringBuilder sb = new StringBuilder();
methods.forEach(m -> sb.append(m.toString()).append("\n\n"));
result = result.replace("#{methods}", sb.toString());
}
return result.replaceAll("\\#\\{.+?\\}", "");
}
public Set<String> getLimitcs() {
return limitcs;
}
public ClassModel addLimitc(String limitc) {
if (limitcs == null) {
limitcs = new HashSet<>();
}
this.limitcs.add(limitc);
return this;
}
public Class<?> getGenericType() {
return genericType;
}
public ClassModel setGenericType(Class<?> genericType) {
this.genericType = genericType;
return this;
}
public String getName() {
return name;
}
public ClassModel setName(String name) {
this.name = name;
this.simpleName = name.substring(name.lastIndexOf(".") + 1);
return this;
}
public String getSimpleName() {
return simpleName;
}
public ClassModel setSimpleName(String simpleName) {
this.simpleName = simpleName;
return this;
}
public Qualifier getQualifier() {
return qualifier;
}
public ClassModel setQualifier(Qualifier qualifier) {
this.qualifier = qualifier;
return this;
}
public List<Modifier> getModifiers() {
return modifiers;
}
public ClassModel addModifier(Modifier modifier) {
if (this.modifiers == null) {
this.modifiers = new ArrayList<>();
}
this.modifiers.add(modifier);
return this;
}
public ClassOrInterface getClassOrInterface() {
return classOrInterface;
}
public ClassModel setClassOrInterface(ClassOrInterface classOrInterface) {
this.classOrInterface = classOrInterface;
return this;
}
public List<AnnotationModel> getAnnotations() {
return annotations;
}
public ClassModel addAnnotation(AnnotationModel annotation) {
if (this.annotations == null) {
this.annotations = new ArrayList<>();
}
this.annotations.add(annotation);
return this;
}
public ClassTypeModel getBaseClass() {
return baseClass;
}
public ClassModel setBaseClass(ClassTypeModel baseClass) {
this.baseClass = baseClass;
return this;
}
public List<ClassTypeModel> getInterfaces() {
return interfaces;
}
public ClassModel addInterface(ClassTypeModel interfacec) {
if (this.interfaces == null) {
this.interfaces = new ArrayList<>();
}
this.interfaces.add(interfacec);
return this;
}
public List<FieldModel> getFields() {
return fields;
}
public ClassModel addField(FieldModel field) {
if (this.fields == null) {
this.fields = new ArrayList<>();
}
this.fields.add(field);
return this;
}
public List<MethodModel> getMethods() {
return methods;
}
public ClassModel addMethod(MethodModel method) {
if (this.methods == null) {
this.methods = new ArrayList<>();
}
this.methods.add(method);
return this;
}
}
ClassTypeModel.java
package com.alan.generate.model;
import com.alan.generate.utils.Utils;
public class ClassTypeModel {
private Class<?> classType;
private String className;
private Class<?> genericType;
private String genericTypeName;
private ClassModel classModel;
public ClassTypeModel() {
}
public ClassTypeModel(ClassModel classModel) {
this.classModel = classModel;
}
@Override
public String toString() {
return className + (Utils.isNotEmpty(genericTypeName) ? "<" + genericTypeName + ">" : "");
}
public String getClassName() {
return className;
}
public ClassTypeModel setClassName(String className) {
this.className = className;
return this;
}
public String getGenericTypeName() {
return genericTypeName;
}
public ClassTypeModel setGenericTypeName(String genericTypeName) {
this.genericTypeName = genericTypeName;
return this;
}
public ClassModel getClassModel() {
return classModel;
}
public void setClassModel(ClassModel classModel) {
this.classModel = classModel;
}
public Class<?> getClassType() {
return classType;
}
public ClassTypeModel setClassType(Class<?> classType) {
if (classModel != null) {
classModel.addLimitc(classType.getName());
}
this.classType = classType;
setClassName(classType.getSimpleName());
return this;
}
public Class<?> getGenericType() {
return genericType;
}
public ClassTypeModel setGenericType(Class<?> genericType) {
this.genericType = genericType;
this.genericTypeName = genericType.getSimpleName();
return this;
}
}
AnnotationModel.java
package com.alan.generate.model;
public class AnnotationModel extends ClassTypeModel {
private String value;
public AnnotationModel() {
}
public AnnotationModel(ClassModel classModel) {
setClassModel(classModel);
}
@Override
public String toString() {
return "@" + getClassType().getSimpleName() + (value == null ? "" : "(" + value + ")");
}
public Class<?> getAnnotationClass() {
return getClassType();
}
public AnnotationModel setAnnotationClass(Class<?> annotationClass) {
setClassType(annotationClass);
return this;
}
public String getValue() {
return value;
}
public AnnotationModel setValue(String value) {
this.value = value;
return this;
}
}
FieldModel.java
package com.alan.generate.model;
import java.util.ArrayList;
import java.util.List;
import com.alan.generate.enums.Modifier;
import com.alan.generate.enums.Qualifier;
import com.alan.generate.utils.Utils;
public class FieldModel {
private Qualifier qualifier;
private List<Modifier> modifiers;
private ClassTypeModel fieldType;
private String fieldName;
private String value;
private List<AnnotationModel> annotations;
private ClassModel classModel;
public FieldModel() {
}
public FieldModel(ClassModel classModel) {
this.classModel = classModel;
}
@Override
public String toString() {
String result = "#{annotations}\t#{qualifier}#{modifiers}#{fieldType} #{fieldName}#{value};";
if (Utils.isNotEmpty(annotations)) {
final StringBuilder sb = new StringBuilder();
annotations.forEach(a -> sb.append("\t").append(a.toString()).append("\n"));
result = result.replace("#{annotations}", sb.toString());
}
if (Utils.isNotEmpty(qualifier))
result = result.replace("#{qualifier}", qualifier.toString() + " ");
if (Utils.isNotEmpty(modifiers)) {
final StringBuilder sb = new StringBuilder();
modifiers.forEach(m -> sb.append(m.toString()).append(" "));
result = result.replace("#{modifiers}", sb.toString());
}
result = result.replace("#{fieldType}", fieldType.toString());
result = result.replace("#{fieldName}", fieldName);
if (Utils.isNotEmpty(value))
result = result.replace("#{value}", " = " + value);
return result.replaceAll("\\#\\{.+?\\}", "");
}
public String valid() {
return Utils.isNotEmpty(fieldType) && Utils.isNotEmpty(fieldName) ? "" : "not valid field";
}
public ClassModel getClassModel() {
return classModel;
}
public FieldModel setClassModel(ClassModel classModel) {
this.classModel = classModel;
return this;
}
public String getValue() {
return value;
}
public FieldModel setValue(String value) {
this.value = value;
return this;
}
public List<AnnotationModel> getAnnotations() {
return annotations;
}
public FieldModel addAnnotation(AnnotationModel annotation) {
if (this.annotations == null) {
this.annotations = new ArrayList<>();
}
this.annotations.add(annotation);
return this;
}
public Qualifier getQualifier() {
return qualifier;
}
public FieldModel setQualifier(Qualifier qualifier) {
this.qualifier = qualifier;
return this;
}
public List<Modifier> getModifiers() {
return modifiers;
}
public FieldModel addModifier(Modifier modifier) {
if (this.modifiers == null) {
this.modifiers = new ArrayList<>();
}
this.modifiers.add(modifier);
return this;
}
public ClassTypeModel getFieldType() {
return fieldType;
}
public FieldModel setFieldType(ClassTypeModel fieldType) {
this.fieldType = fieldType;
return this;
}
public String getFieldName() {
return fieldName;
}
public FieldModel setFieldName(String fieldName) {
this.fieldName = fieldName;
return this;
}
public FieldModel generateSetMethod() {
MethodModel methodModel = new MethodModel();
methodModel.setQualifier(Qualifier.publicQ)
.setMethodName("set" + this.fieldName.substring(0, 1).toUpperCase() + this.fieldName.substring(1))
.addParamModel(new ParamModel().setParamType(this.fieldType).setParamName(this.fieldName))
.addStatement("this." + this.fieldName + " = " + this.fieldName);
classModel.addMethod(methodModel);
return this;
}
public FieldModel generateGetMethod() {
MethodModel methodModel = new MethodModel();
methodModel.setQualifier(Qualifier.publicQ).setReturnType(this.fieldType)
.setMethodName("get" + this.fieldName.substring(0, 1).toUpperCase() + this.fieldName.substring(1))
.addStatement("return " + this.fieldName);
classModel.addMethod(methodModel);
return this;
}
}
MethodModel.java
package com.alan.generate.model;
import java.util.ArrayList;
import java.util.List;
import com.alan.generate.enums.Modifier;
import com.alan.generate.enums.Qualifier;
import com.alan.generate.utils.Utils;
public class MethodModel {
private Qualifier qualifier;
private List<Modifier> modifiers;
private ClassTypeModel returnType;
private String methodName;
private List<ParamModel> paramModels;
private List<String> statements;
private List<AnnotationModel> annotations;
@Override
public String toString() {
String result = "#{annotations}\t#{qualifier}#{modifiers}#{returnType}#{methodName} (#{paramModels}) {\n#{statements}\t}";
if (Utils.isNotEmpty(annotations)) {
final StringBuilder sb = new StringBuilder();
annotations.forEach(a -> sb.append("\t").append(a.toString()).append("\n"));
result = result.replace("#{annotations}", sb.toString());
}
if (Utils.isNotEmpty(qualifier))
result = result.replace("#{qualifier}", qualifier.toString() + " ");
if (Utils.isNotEmpty(modifiers)) {
final StringBuilder sb = new StringBuilder();
modifiers.forEach(m -> sb.append(m.toString()).append(" "));
result = result.replace("#{modifiers}", sb.toString());
}
if (Utils.isNotEmpty(returnType)) {
result = result.replace("#{returnType}", returnType.toString() + " ");
} else {
result = result.replace("#{returnType}", "void ");
}
result = result.replace("#{methodName}", methodName);
if (Utils.isNotEmpty(paramModels)) {
final StringBuilder sb = new StringBuilder();
paramModels.forEach(p -> sb.append(p.toString()).append(", "));
result = result.replace("#{paramModels}", sb.substring(0, sb.lastIndexOf(",")));
}
if (Utils.isNotEmpty(statements)) {
StringBuilder sb = new StringBuilder();
statements.forEach(s -> {
if (s.lastIndexOf(";") != s.length() - 1)
s += ";";
sb.append("\t\t").append(s).append("\n");
});
result = result.replace("#{statements}", sb.toString());
}
return result.replaceAll("\\#\\{.+?\\}", "");
}
public String valid() {
return Utils.isNotEmpty(methodName) ? "" : "not valid method";
}
public List<AnnotationModel> getAnnotations() {
return annotations;
}
public MethodModel addAnnotation(AnnotationModel annotation) {
if (this.annotations == null) {
this.annotations = new ArrayList<>();
}
this.annotations.add(annotation);
return this;
}
public Qualifier getQualifier() {
return qualifier;
}
public MethodModel setQualifier(Qualifier qualifier) {
this.qualifier = qualifier;
return this;
}
public List<Modifier> getModifiers() {
return modifiers;
}
public MethodModel addModifier(Modifier modifier) {
if (this.modifiers == null) {
this.modifiers = new ArrayList<>();
}
this.modifiers.add(modifier);
return this;
}
public ClassTypeModel getReturnType() {
return returnType;
}
public MethodModel setReturnType(ClassTypeModel returnType) {
this.returnType = returnType;
return this;
}
public String getMethodName() {
return methodName;
}
public MethodModel setMethodName(String methodName) {
this.methodName = methodName;
return this;
}
public List<ParamModel> getParamModels() {
return paramModels;
}
public MethodModel addParamModel(ParamModel paramModel) {
if (this.paramModels == null) {
this.paramModels = new ArrayList<>();
}
this.paramModels.add(paramModel);
return this;
}
public List<String> getStatements() {
return statements;
}
public MethodModel addStatement(String statement) {
if (this.statements == null) {
this.statements = new ArrayList<>();
}
this.statements.add(statement);
return this;
}
}
ParamModel.java
package com.alan.generate.model;
import java.util.ArrayList;
import java.util.List;
import com.alan.generate.utils.Utils;
public class ParamModel {
private ClassTypeModel paramType;
private String paramName;
private List<AnnotationModel> annotations;
@Override
public String toString() {
String result = "#{annotations}#{paramType}#{paramTypeGenericType} #{paramName}";
if (Utils.isNotEmpty(annotations)) {
final StringBuilder sb = new StringBuilder();
annotations.forEach(a -> sb.append(a.toString()).append(" "));
result = result.replace("#{annotations}", sb.toString());
}
result = result.replace("#{paramType}", paramType.toString());
result = result.replace("#{paramName}", paramName);
return result.replaceAll("\\#\\{.+?\\}", "");
}
public String valid() {
return Utils.isNotEmpty(paramType) && Utils.isNotEmpty(paramName) ? "" : "not valid param";
}
public List<AnnotationModel> getAnnotations() {
return annotations;
}
public ParamModel addAnnotation(AnnotationModel annotation) {
if (this.annotations == null) {
this.annotations = new ArrayList<>();
}
this.annotations.add(annotation);
return this;
}
public ClassTypeModel getParamType() {
return paramType;
}
public ParamModel setParamType(ClassTypeModel paramType) {
this.paramType = paramType;
return this;
}
public String getParamName() {
return paramName;
}
public ParamModel setParamName(String paramName) {
this.paramName = paramName;
return this;
}
}
数据库对应的model
TableModel.java
package com.alan.generate.model;
import java.util.ArrayList;
import java.util.List;
import com.alan.generate.utils.Utils;
/**
* 用于存储数据库中原生数据
*
* @author alan
*
*/
public class TableModel {
private String tableName;
private ColumnModel primaryKey;
// 不包含主键列
private List<ColumnModel> columns;
public TableModel() {
}
public TableModel(String tableName) {
this.tableName = tableName;
}
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public ColumnModel getPrimaryKey() {
return primaryKey;
}
public void setPrimaryKey(ColumnModel primaryKey) {
this.primaryKey = primaryKey;
}
public List<ColumnModel> getColumns() {
return columns;
}
public void addColumn(ColumnModel column) {
if (columns == null) {
columns = new ArrayList<>();
}
this.columns.add(column);
}
@Override
public String toString() {
String result = "table name is: \n" + tableName + "\n\nprimary key is: \n" + primaryKey.getColumnName() + " "
+ primaryKey.getDataType() + "\n\n";
if (Utils.isNotEmpty(columns)) {
StringBuilder sb = new StringBuilder("columns are: \n");
columns.forEach(c -> sb.append(c.getColumnName()).append(" ").append(c.getDataType()).append("\n"));
result += sb.toString();
}
return result;
}
}
ColumnModel.java
package com.alan.generate.model;
public class ColumnModel {
private String columnName;
private String dataType;
public String getColumnName() {
return columnName;
}
public ColumnModel setColumnName(String columnName) {
this.columnName = columnName;
return this;
}
public String getDataType() {
return dataType;
}
public ColumnModel setDataType(String dataType) {
this.dataType = dataType;
return this;
}
}
(2)枚举类
定义几个枚举类,在包com.alan.generate.enums下:
Qualifier.java
package com.alan.generate.enums;
public enum Qualifier {
publicQ("public"), privateQ("private"), protectedQ("protected"), defaultQ("");
private String code;
private Qualifier(String code) {
this.code = code;
}
public String getCode() {
return code;
}
@Override
public String toString() {
return code;
}
public static void main(String[] args) {
System.out.println(privateQ.toString());
}
}
Modifier.java
这里就定义了两个,还有几个不怎么用的,就不定义了。
package com.alan.generate.enums;
public enum Modifier {
staticM("static"), finalM("final");
private String code;
private Modifier(String code) {
this.code = code;
}
public String getCode() {
return code;
}
@Override
public String toString() {
return code;
}
}
ClassOrInterface.java
package com.alan.generate.enums;
public enum ClassOrInterface {
classC("class"), interfaceC("interface"), aInterfaceC("@interface");
private String code;
private ClassOrInterface(String code) {
this.code = code;
}
public String getCode() {
return code;
}
@Override
public String toString() {
return code;
}
}
(3)常量类
用于存放数据库连接及路径配置。
Constant.java
package com.alan.generate.main;
public class Constant {
public static final String entityPkg = "com.alan.entity";
public static final String mapperPkg = "com.alan.mapper";
public static final String daoPkg = "com.alan.dao";
public static final String servicePkg = "com.alan.service";
public static final String controllerPkg = "com.alan.controller";
public static final String daoProject = "alan-springmvc-dao";
public static final String serviceProject = "alan-springmvc-service";
public static final String webProject = "alan-springmvc-web";
public static String driverClassName = "com.mysql.jdbc.Driver";
public static String url = "jdbc:mysql://127.0.0.1:3306/alan_springmvc";
public static String username = "root";
public static String password = "1234";
}
(4)帮助类
Utils.java
主要用于处理判空、数据库下划线转驼峰等。
package com.alan.generate.utils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Utils {
public static Map<String, String> dataTypeMapping;
static {
dataTypeMapping = new HashMap<String, String>();
dataTypeMapping.put("varchar", "String");
dataTypeMapping.put("bigint", "Long");
dataTypeMapping.put("int", "Long");
dataTypeMapping.put("datetime", "java.util.Date");
dataTypeMapping.put("tinyint", "Integer");
dataTypeMapping.put("decimal", "java.math.BigDecimal");
dataTypeMapping.put("double", "Double");
dataTypeMapping.put("char", "String");
dataTypeMapping.put("text", "String");
dataTypeMapping.put("float", "Double");
dataTypeMapping.put("timestamp", "java.util.Date");
}
public static boolean isNotEmpty(Object object) {
return !isEmpty(object);
}
public static boolean isEmpty(Object object) {
if (object == null)
return true;
if (object instanceof String)
return String.valueOf(object).length() == 0;
if (object instanceof List<?>)
return ((List<?>) object).size() == 0;
return false;
}
public static String firstUpper(String string) {
return string.substring(0, 1).toUpperCase() + string.substring(1);
}
public static String firstLower(String string) {
return string.substring(0, 1).toLowerCase() + string.substring(1);
}
public static String fromUnderlineToCamel(String string) {
char[] src = string.toLowerCase().toCharArray();
char[] des = new char[src.length];
boolean flag = false;
int i = 0;
for (char c : src) {
if (c == '_') {
flag = true;
continue;
}
if (flag) {
c = Character.toUpperCase(c);
flag = false;
}
des[i++] = c;
}
return String.valueOf(des).trim();
}
public static String getTableName(String dbTableName) {
return firstUpper(fromUnderlineToCamel(dbTableName));
}
public static String getColumnName(String dbColumnName) {
return fromUnderlineToCamel(dbColumnName);
}
public static String join(List<String> list, String seperator) {
String result = list.stream().reduce("", (sum, s) -> sum = sum + s + seperator);
return result.substring(0, result.lastIndexOf(seperator));
}
public static void main(String[] args) {
// String result = firstUpper("userInfo");
// System.out.println(result);
// System.out.println(fromUnderlineToCamel("user_info_table"));
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("b");
list.add("b");
list.add("b");
System.out.println(join(list, ", "));
}
}
(5)生成代码帮助类
GenerateHelper.java
package com.alan.generate.utils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.alan.generate.enums.ClassOrInterface;
import com.alan.generate.enums.Qualifier;
import com.alan.generate.main.Constant;
import com.alan.generate.model.AnnotationModel;
import com.alan.generate.model.ClassModel;
import com.alan.generate.model.ClassTypeModel;
import com.alan.generate.model.ColumnModel;
import com.alan.generate.model.FieldModel;
import com.alan.generate.model.MethodModel;
import com.alan.generate.model.TableModel;
import com.alan.parent.controller.BaseController;
import com.alan.parent.dao.BaseDao;
import com.alan.parent.dao.BaseDaoImpl;
import com.alan.parent.mapper.BaseMapper;
import com.alan.parent.service.BaseService;
import com.alan.parent.service.BaseServiceImpl;
public class GenerateHelper {
public static final String entityPkg = Constant.entityPkg;
public static final String mapperPkg = Constant.mapperPkg;
public static final String daoPkg = Constant.daoPkg;
public static final String servicePkg = Constant.servicePkg;
public static final String controllerPkg = Constant.controllerPkg;
public static final String daoProject = Constant.daoProject;
public static final String serviceProject = Constant.serviceProject;
public static final String webProject = Constant.webProject;
public static String daoPath;
public static String servicePath;
public static String webPath;
public static File historyFile;
public static boolean flag = false;
static {
File directory = new File("");// 参数为空
File parentDir;
try {
parentDir = new File(new File(directory.getCanonicalPath()).getParent());
File[] files = parentDir.listFiles();
for (File file : files) {
if (file.isDirectory()) {
if (file.getName().equals(daoProject))
daoPath = file.getAbsolutePath();
if (file.getName().equals(serviceProject))
servicePath = file.getAbsolutePath();
if (file.getName().equals(webProject))
webPath = file.getAbsolutePath();
}
}
historyFile = new File(daoPath + "\\src\\main\\resources\\history.txt");
} catch (IOException e) {
e.printStackTrace();
}
}
public static String getDaoInterfaceCode(String entityName) {
ClassModel classModel = new ClassModel();
classModel.setName(daoPkg + "." + entityName + "Dao").addLimitc(entityPkg + "." + entityName)
.setQualifier(Qualifier.publicQ).setClassOrInterface(ClassOrInterface.interfaceC).setBaseClass(
new ClassTypeModel(classModel).setClassType(BaseDao.class).setGenericTypeName(entityName));
return classModel.toString();
}
public static String getDaoImplCode(String entityName) {
ClassModel classModel = new ClassModel();
classModel.setName(daoPkg + ".impl." + entityName + "DaoImpl")
.addAnnotation(new AnnotationModel(classModel).setAnnotationClass(Repository.class)
.setValue("\"" + Utils.firstLower(entityName) + "Dao\""))
.setQualifier(Qualifier.publicQ).setClassOrInterface(ClassOrInterface.classC)
.setBaseClass(
new ClassTypeModel(classModel).setClassType(BaseDaoImpl.class).setGenericTypeName(entityName))
.addInterface(new ClassTypeModel().setClassName(entityName + "Dao"))
.addField(new FieldModel(classModel)
.addAnnotation(new AnnotationModel(classModel).setAnnotationClass(Resource.class))
.setQualifier(Qualifier.privateQ)
.setFieldType(new ClassTypeModel().setClassName(entityName + "Mapper")).setFieldName(
Utils.firstLower(entityName) + "Mapper"))
.addMethod(new MethodModel()
.addAnnotation(new AnnotationModel(classModel).setAnnotationClass(Override.class))
.setQualifier(Qualifier.publicQ)
.setReturnType(new ClassTypeModel(classModel).setClassType(BaseMapper.class)
.setGenericTypeName(entityName))
.setMethodName("init").addStatement("return " + Utils.firstLower(entityName) + "Mapper"))
.addLimitc(entityPkg + "." + entityName).addLimitc(daoPkg + "." + entityName + "Dao")
.addLimitc(mapperPkg + "." + entityName + "Mapper");
return classModel.toString();
}
public static String getMapperCode(String entityName) {
ClassModel classModel = new ClassModel();
classModel.setName(mapperPkg + "." + entityName + "Mapper").addLimitc(entityPkg + "." + entityName)
.setQualifier(Qualifier.publicQ).setClassOrInterface(ClassOrInterface.interfaceC).setBaseClass(
new ClassTypeModel(classModel).setClassType(BaseMapper.class).setGenericTypeName(entityName));
return classModel.toString();
}
public static String getServiceCode(String entityName) {
ClassModel classModel = new ClassModel();
classModel.setName(servicePkg + "." + entityName + "Service").addLimitc(entityPkg + "." + entityName)
.setQualifier(Qualifier.publicQ).setClassOrInterface(ClassOrInterface.interfaceC).setBaseClass(
new ClassTypeModel(classModel).setClassType(BaseService.class).setGenericTypeName(entityName));
return classModel.toString();
}
public static String getServiceImplCode(String entityName) {
ClassModel classModel = new ClassModel();
classModel.setName(servicePkg + ".impl." + entityName + "ServiceImpl")
.addAnnotation(new AnnotationModel(classModel).setAnnotationClass(Service.class)
.setValue("\"" + Utils.firstLower(entityName) + "Service\""))
.setQualifier(Qualifier.publicQ).setClassOrInterface(ClassOrInterface.classC)
.setBaseClass(new ClassTypeModel(classModel).setClassType(BaseServiceImpl.class)
.setGenericTypeName(entityName))
.addInterface(new ClassTypeModel().setClassName(entityName + "Service"))
.addField(new FieldModel(classModel)
.addAnnotation(new AnnotationModel(classModel).setAnnotationClass(Resource.class)).setQualifier(
Qualifier.privateQ)
.setFieldType(new ClassTypeModel().setClassName(entityName + "Dao")).setFieldName(
Utils.firstLower(entityName) + "Dao"))
.addMethod(new MethodModel()
.addAnnotation(new AnnotationModel(classModel).setAnnotationClass(Override.class))
.setQualifier(Qualifier.publicQ)
.setReturnType(new ClassTypeModel(classModel).setClassType(BaseDao.class)
.setGenericTypeName(entityName))
.setMethodName("init").addStatement("return " + Utils.firstLower(entityName) + "Dao"))
.addLimitc(entityPkg + "." + entityName).addLimitc(servicePkg + "." + entityName + "Service")
.addLimitc(daoPkg + "." + entityName + "Dao");
return classModel.toString();
}
public static String getControllerCode(String entityName) {
ClassModel classModel = new ClassModel();
classModel.setName(controllerPkg + "." + entityName + "Controller")
.addAnnotation(new AnnotationModel(classModel).setAnnotationClass(RestController.class))
.addAnnotation(new AnnotationModel(classModel).setAnnotationClass(RequestMapping.class)
.setValue("\"/" + Utils.firstLower(entityName) + "\""))
.setQualifier(Qualifier.publicQ).setClassOrInterface(ClassOrInterface.classC)
.setBaseClass(new ClassTypeModel(classModel).setClassType(BaseController.class)
.setGenericTypeName(entityName))
.addField(new FieldModel(classModel)
.addAnnotation(new AnnotationModel(classModel).setAnnotationClass(Resource.class))
.setQualifier(Qualifier.privateQ)
.setFieldType(new ClassTypeModel().setClassName(entityName + "Service")).setFieldName(
Utils.firstLower(entityName) + "Service"))
.addMethod(new MethodModel()
.addAnnotation(new AnnotationModel(classModel).setAnnotationClass(Override.class))
.setQualifier(Qualifier.publicQ)
.setReturnType(new ClassTypeModel(classModel).setClassType(BaseService.class)
.setGenericTypeName(entityName))
.setMethodName("init").addStatement("return " + Utils.firstLower(entityName) + "Service"))
.addLimitc(entityPkg + "." + entityName).addLimitc(servicePkg + "." + entityName + "Service");
return classModel.toString();
}
public static String getEntityCode(TableModel tableModel) {
ClassModel classModel = new ClassModel();
classModel.setName(entityPkg + "." + Utils.getTableName(tableModel.getTableName()))
.setQualifier(Qualifier.publicQ).setClassOrInterface(ClassOrInterface.classC);
classModel
.addField(
new FieldModel().setQualifier(Qualifier.privateQ)
.setFieldType(new ClassTypeModel(classModel).setClassName(
Utils.dataTypeMapping.get(tableModel.getPrimaryKey().getDataType())))
.setFieldName(Utils.getColumnName(tableModel.getPrimaryKey().getColumnName()))
.setClassModel(classModel).generateGetMethod().generateSetMethod());
tableModel.getColumns()
.forEach(
c -> classModel.addField(new FieldModel().setQualifier(Qualifier.privateQ)
.setFieldType(new ClassTypeModel(classModel)
.setClassName(Utils.dataTypeMapping.get(c.getDataType())))
.setFieldName(Utils.getColumnName(c.getColumnName())).setClassModel(classModel)
.generateGetMethod().generateSetMethod()));
return classModel.toString();
}
public static String getMapperXmlCode(TableModel tableModel) {
String result = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\" >\n<mapper namespace=\"#{mapperPkg}.#{entityName}Mapper\">\n\n\t<sql id=\"columns\">\n\t\t#{columns}\n\t</sql>\n\n\t<sql id=\"columns-place-holder\">\n\t\t#{columnsPlaceHolder}\n\t</sql>\n\n\t<resultMap type=\"#{entityPkg}.#{entityName}\" id=\"#{lowerEntityName}Entity\">\n\t\t#{primaryKeyColumnMap}\n#{otherColumnsMap}\t</resultMap>\n\t<select id=\"queryById\" resultMap=\"#{lowerEntityName}Entity \">\n\t\tselect <include refid=\"columns\"></include>\n\t\tfrom #{tableName}\n\t\twhere #{dbPrimaryKey}=#{javaPrimaryKey}\n\t</select>\n\n\t<select id=\"queryAll\" resultMap=\"#{lowerEntityName}Entity\">\n\t\tselect <include refid=\"columns\"></include>\n\t\tfrom #{tableName}\n\t</select>\n\n\t<insert id=\"add\">\n\t\tinsert into #{tableName} (<include refid=\"columns\"></include>)\n\t\tvalues(<include refid=\"columns-place-holder\"></include>)\n\t</insert>\n\n\t<update id=\"update\" parameterType=\"#{entityPkg}.#{entityName}\">\n\t\tupdate #{tableName}\n\t\tset #{dbPrimaryKey} = #{javaPrimaryKey}\n#{updateColumnMap}\n\t\twhere #{dbPrimaryKey} = #{javaPrimaryKey}\n\t</update>\n\n\t<delete id=\"delete\">\n\t\tdelete from #{tableName} where #{dbPrimaryKey} = #{javaPrimaryKey}\n\t</delete>\n\n</mapper>";
result = result.replace("#{mapperPkg}", mapperPkg);
result = result.replaceAll("\\#\\{entityName\\}", Utils.getTableName(tableModel.getTableName()));
StringBuilder sbColumns = new StringBuilder();
StringBuilder sbColumnsPlaceHolder = new StringBuilder();
StringBuilder sbResultMap = new StringBuilder();
StringBuilder sbUpdateMap = new StringBuilder();
sbColumns.append(tableModel.getPrimaryKey().getColumnName()).append(", ");
sbColumnsPlaceHolder.append("#{").append(Utils.getColumnName(tableModel.getPrimaryKey().getColumnName()))
.append("}, ");
tableModel.getColumns().forEach(c -> {
sbColumns.append(c.getColumnName()).append(", ");
sbColumnsPlaceHolder.append("#{").append(Utils.getColumnName(c.getColumnName())).append("}, ");
sbResultMap.append("\t\t<result column=\"" + c.getColumnName() + "\" property=\""
+ Utils.getColumnName(c.getColumnName()) + "\" />\n");
sbUpdateMap.append("\n\t\t<if test=\"" + Utils.getColumnName(c.getColumnName()) + " != null\">\n\t\t\t,"
+ c.getColumnName() + " = #{" + Utils.getColumnName(c.getColumnName()) + "}\n\t\t</if>");
});
result = result.replace("#{columns}", sbColumns.subSequence(0, sbColumns.lastIndexOf(",")));
result = result.replace("#{columnsPlaceHolder}",
sbColumnsPlaceHolder.subSequence(0, sbColumnsPlaceHolder.lastIndexOf(",")));
result = result.replaceAll("\\#\\{entityPkg\\}", entityPkg);
result = result.replaceAll("\\#\\{lowerEntityName\\}",
Utils.firstLower(Utils.getTableName(tableModel.getTableName())));
result = result.replace("#{primaryKeyColumnMap}", "<id column=\"" + tableModel.getPrimaryKey().getColumnName()
+ "\" property=\"" + Utils.getColumnName(tableModel.getPrimaryKey().getColumnName()) + "\" />");
result = result.replace("#{otherColumnsMap}", sbResultMap.toString());
result = result.replaceAll("\\#\\{dbPrimaryKey\\}", tableModel.getPrimaryKey().getColumnName());
result = result.replaceAll("\\#\\{javaPrimaryKey\\}",
"#{" + Utils.getColumnName(tableModel.getPrimaryKey().getColumnName()) + "}");
result = result.replaceAll("\\#\\{tableName\\}", tableModel.getTableName());
result = result.replace("#{updateColumnMap}", sbUpdateMap.toString());
return result;
}
public static void generateDaoInterface(String tableName) {
String code = getDaoInterfaceCode(Utils.getTableName(tableName));
String path = daoPath + "\\src\\main\\java\\com\\alan\\dao\\" + Utils.getTableName(tableName) + "Dao.java";
createFile(path, code);
}
public static void generateDaoImpl(String tableName) {
String code = getDaoImplCode(Utils.getTableName(tableName));
String path = daoPath + "\\src\\main\\java\\com\\alan\\dao\\impl\\" + Utils.getTableName(tableName)
+ "DaoImpl.java";
createFile(path, code);
}
public static void generateMapper(String tableName) {
String code = getMapperCode(Utils.getTableName(tableName));
String path = daoPath + "\\src\\main\\java\\com\\alan\\mapper\\" + Utils.getTableName(tableName)
+ "Mapper.java";
createFile(path, code);
}
public static void generateService(String tableName) {
String code = getServiceCode(Utils.getTableName(tableName));
String path = servicePath + "\\src\\main\\java\\com\\alan\\service\\" + Utils.getTableName(tableName)
+ "Service.java";
createFile(path, code);
}
public static void generateServiceImpl(String tableName) {
String code = getServiceImplCode(Utils.getTableName(tableName));
String path = servicePath + "\\src\\main\\java\\com\\alan\\service\\impl\\" + Utils.getTableName(tableName)
+ "ServiceImpl.java";
createFile(path, code);
}
public static void generateController(String tableName) {
String code = getControllerCode(Utils.getTableName(tableName));
String path = webPath + "\\src\\main\\java\\com\\alan\\controller\\" + Utils.getTableName(tableName)
+ "Controller.java";
createFile(path, code);
}
public static void generateEntity(TableModel tableModel) {
String code = getEntityCode(tableModel);
String path = daoPath + "\\src\\main\\java\\com\\alan\\entity\\" + Utils.getTableName(tableModel.getTableName())
+ ".java";
createFile(path, code);
}
public static void generateMapperXml(TableModel tableModel) {
String code = getMapperXmlCode(tableModel);
String path = daoPath + "\\src\\main\\resources\\mapper\\" + Utils.getTableName(tableModel.getTableName())
+ "Mapper.xml";
createFile(path, code);
}
public static void createFile(String path, String code) {
File file = new File(path);
try {
file.createNewFile();
FileOutputStream output = new FileOutputStream(file);
output.write(code.getBytes());
output.close();
writeHistory(path);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void writeHistory(String path) {
try {
if (!historyFile.exists()) {
historyFile.createNewFile();
}
FileWriter writer = new FileWriter(historyFile, flag);
writer.write(path + "\n");
writer.close();
flag = true;
} catch (Exception e) {
e.printStackTrace();
}
}
public static void deleteFile(String path) {
File file = new File(path);
file.delete();
}
public static void deleteFilesJustGenerated() {
try {
if (historyFile.exists()) {
BufferedReader reader = new BufferedReader(new FileReader(historyFile));
String line = null;
while ((line = reader.readLine()) != null) {
deleteFile(line);
}
reader.close();
historyFile.delete();
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void generate(List<String> layers, List<String> tables) {
tables.forEach(t -> {
layers.forEach(l -> {
if (l.equals("Mapper")) {
TableModel tableModel = JdbcHelper.getTableInfo(t);
generateEntity(tableModel);
generateMapperXml(tableModel);
generateMapper(t);
}
if (l.equals("Dao")) {
generateDaoInterface(t);
generateDaoImpl(t);
}
if (l.equals("Service")) {
generateService(t);
generateServiceImpl(t);
}
if (l.equals("Controller")) {
generateController(t);
}
});
});
};
public static void main(String[] args) throws IOException {
// testGenerateMapXml();
// deleteFile("E:\\workspace\\my-springmvc\\alan-springmvc\\alan-springmvc-dao\\src\\main\\resources\\mapper\\UserAddressMapper.xml");
deleteFilesJustGenerated();
}
public static void testGenerateMapXml() {
TableModel tableModel = new TableModel();
tableModel.setTableName("user_address_mobile");
tableModel.setPrimaryKey(new ColumnModel().setColumnName("user_id").setDataType("bigint"));
tableModel.addColumn(new ColumnModel().setColumnName("user_name").setDataType("varchar"));
tableModel.addColumn(new ColumnModel().setColumnName("mobile").setDataType("varchar"));
tableModel.addColumn(new ColumnModel().setColumnName("user_age").setDataType("int"));
generateMapperXml(tableModel);
generateEntity(tableModel);
}
}
(6)数据库连接帮助类
JdbcHelper.java
package com.alan.generate.utils;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import com.alan.generate.main.Constant;
import com.alan.generate.model.ColumnModel;
import com.alan.generate.model.TableModel;
public class JdbcHelper {
private static String driverClassName = Constant.driverClassName;
private static String url = Constant.url;
private static String username = Constant.username;
private static String password = Constant.password;
static {
try {
Class.forName(driverClassName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() {
try {
return DriverManager.getConnection(url, username, password);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public static TableModel getTableInfo(String tableName) {
if (Utils.isEmpty(tableName))
return null;
TableModel tableModel = new TableModel(tableName);
String sql = "select column_name, data_type, column_key from information_schema.columns where table_name = ? order by ordinal_position";
ResultSet rs = query(sql, tableName);
try {
while (rs.next()) {
if (Utils.isNotEmpty(rs.getString(3)) && "PRI".equals(rs.getString(3))) {
tableModel.setPrimaryKey(
new ColumnModel().setColumnName(rs.getString(1)).setDataType(rs.getString(2)));
} else {
tableModel.addColumn(new ColumnModel().setColumnName(rs.getString(1)).setDataType(rs.getString(2)));
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return tableModel;
}
public static List<String> getAllTables(String tableSchema) {
List<String> list = new ArrayList<>();
if (Utils.isEmpty(tableSchema))
return list;
String sql = "select table_name from information_schema.tables where table_schema = ? order by table_name";
ResultSet rs = query(sql, tableSchema);
try {
while (rs.next()) {
list.add(rs.getString(1));
}
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
public static List<String> getTablesByFilter(String tableSchema, String filter) {
if (filter == null)
filter = "";
List<String> list = new ArrayList<>();
String sql = "select table_name from information_schema.tables where table_schema = ? and table_name like ? order by table_name";
ResultSet rs = query(sql, tableSchema, "%" + filter + "%");
try {
while (rs.next()) {
list.add(rs.getString(1));
}
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
public static List<String> getAllSchema() {
List<String> list = new ArrayList<>();
String sql = "select schema_name from information_schema.schemata order by schema_name";
ResultSet rs = query(sql);
try {
while (rs.next()) {
list.add(rs.getString(1));
}
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
private static ResultSet query(String sql, Object... objects) {
try {
PreparedStatement ps = getConnection().prepareStatement(sql);
if (objects != null && objects.length != 0) {
for (int i = 0; i < objects.length; i++) {
ps.setObject(i + 1, objects[i]);
}
}
return ps.executeQuery();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
}
(7)生成代码的UI界面
GenerateUI.java
package com.alan.generate.utils;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
public class GenerateUI extends JFrame {
private static final long serialVersionUID = 1L;
private JComboBox<String> schemaList;
private JTextField filterText;
private JPanel contentPanel;
private JPanel topPanel;
private JPanel middlePanel;
private JPanel bottomPanel;
private JPanel tablePanel;
private JCheckBox mapperCheckBox;
private JCheckBox daoCheckBox;
private JCheckBox serviceCheckBox;
private JCheckBox controllerCheckBox;
private JCheckBox allCheckBox;
private Map<String, Boolean> bottomCheckBoxSelected = new HashMap<>();
private Map<String, Boolean> tableCheckBoxSelected = new HashMap<>();
private TableCheckBoxListener tableCheckBoxListener = new TableCheckBoxListener();
public void getUI() {
createTopPanel();
createMiddlePanel();
createBottomPanel();
this.setTitle("自动生成代码");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setBounds(400, 250, 500, 600);
contentPanel = new JPanel();
contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
this.setContentPane(contentPanel);
contentPanel.setLayout(new GridBagLayout());
GridBagConstraints c1 = new GridBagConstraints();
c1.gridx = 0;
c1.gridy = 0;
c1.weightx = 1.0;
c1.weighty = 0;
c1.fill = GridBagConstraints.HORIZONTAL;
contentPanel.add(topPanel, c1);
GridBagConstraints c2 = new GridBagConstraints();
c2.gridx = 0;
c2.gridy = 1;
c2.weightx = 1.0;
c2.weighty = 1.0;
c2.fill = GridBagConstraints.BOTH;
// 加入 middlePanel
contentPanel.add(middlePanel, c2);
GridBagConstraints c3 = new GridBagConstraints();
c3.gridx = 0;
c3.gridy = 2;
c3.weightx = 1.0;
c3.weighty = 0;
c3.fill = GridBagConstraints.HORIZONTAL;
// 加入 bottomPanel
contentPanel.add(bottomPanel, c3);
this.setVisible(true);
}
public Component getSchemaLabel() {
return new JLabel("schema: ");
}
public Component getSchemaList() {
schemaList = new JComboBox<>();
schemaList.setLightWeightPopupEnabled(false);
schemaList.addItem("");
JdbcHelper.getAllSchema().forEach(s -> schemaList.addItem(s));
schemaList.addActionListener(new ActionListener() {
@SuppressWarnings("unchecked")
@Override
public void actionPerformed(ActionEvent e) {
String selectedItem = ((JComboBox<String>) (e.getSource())).getSelectedItem().toString().trim();
updateTableList(selectedItem);
if (selectedItem.length() == 0) {
filterText.setEditable(false);
} else {
filterText.setEditable(true);
}
filterText.setText(null);
}
});
return schemaList;
}
public Component getFilterLabel() {
return new JLabel("filter: ");
}
public Component getFilterText() {
filterText = new JTextField(20);
filterText.setEditable(false);
filterText.getDocument().addDocumentListener(new DocumentListener() {
private void change() {
updateTableListByFilter(schemaList.getSelectedItem().toString().trim(), filterText.getText().trim());
}
@Override
public void removeUpdate(DocumentEvent e) {
change();
}
@Override
public void insertUpdate(DocumentEvent e) {
change();
}
@Override
public void changedUpdate(DocumentEvent e) {
change();
}
});
return filterText;
}
public void createTopPanel() {
topPanel = new JPanel();
topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.Y_AXIS));
JPanel linePanel = new JPanel();
linePanel.setLayout(new BoxLayout(linePanel, BoxLayout.X_AXIS));
linePanel.add(getSchemaLabel());
linePanel.add(getSchemaList());
linePanel.add(Box.createHorizontalGlue());
linePanel.add(getFilterLabel());
linePanel.add(getFilterText());
topPanel.add(Box.createVerticalStrut(10));
topPanel.add(linePanel);
topPanel.add(Box.createVerticalStrut(10));
}
public void createMiddlePanel() {
middlePanel = new JPanel();
middlePanel.setLayout(new BoxLayout(middlePanel, BoxLayout.Y_AXIS));
tablePanel = new JPanel();
tablePanel.setLayout(new BoxLayout(tablePanel, BoxLayout.Y_AXIS));
JScrollPane scrollPane = new JScrollPane(tablePanel);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
middlePanel.add(scrollPane);
}
public void createBottomPanel() {
bottomPanel = new JPanel();
bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.Y_AXIS));
JPanel linePanel = new JPanel();
linePanel.setLayout(new BoxLayout(linePanel, BoxLayout.X_AXIS));
BottomCheckBoxListener cBoxListener = new BottomCheckBoxListener();
mapperCheckBox = new JCheckBox("Mapper");
daoCheckBox = new JCheckBox("Dao");
serviceCheckBox = new JCheckBox("Service");
controllerCheckBox = new JCheckBox("Controller");
allCheckBox = new JCheckBox("All");
mapperCheckBox.addActionListener(cBoxListener);
daoCheckBox.addActionListener(cBoxListener);
serviceCheckBox.addActionListener(cBoxListener);
controllerCheckBox.addActionListener(cBoxListener);
allCheckBox.addActionListener(cBoxListener);
linePanel.add(mapperCheckBox);
linePanel.add(Box.createHorizontalStrut(20));
linePanel.add(daoCheckBox);
linePanel.add(Box.createHorizontalStrut(20));
linePanel.add(serviceCheckBox);
linePanel.add(Box.createHorizontalStrut(20));
linePanel.add(controllerCheckBox);
linePanel.add(Box.createHorizontalStrut(20));
linePanel.add(allCheckBox);
linePanel.add(Box.createHorizontalStrut(140));
JPanel linePanel2 = new JPanel();
linePanel2.setLayout(new BoxLayout(linePanel2, BoxLayout.X_AXIS));
linePanel2.add(new JLabel("注:生成Mapper时会重新生成Entity、Mapper.java及Mapper.xml文件!"));
JButton button = new JButton("生成");
linePanel2.add(button);
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
List<String> layers = new ArrayList<>();
List<String> tables = new ArrayList<>();
bottomCheckBoxSelected.forEach((id, val) -> {
if (val)
layers.add(id);
});
if (layers.size() == 0) {
JOptionPane.showMessageDialog(null, "您还没有选择生成哪些层!");
return;
}
tableCheckBoxSelected.forEach((id, val) -> {
if (val)
tables.add(id);
});
if (tables.size() == 0) {
JOptionPane.showMessageDialog(null, "您还没有选择表!");
return;
}
int response = JOptionPane.showConfirmDialog(null,
"您选择的信息如下:\n层:" + Utils.join(layers, ", ") + "\n表:" + Utils.join(tables, ", "), "确定生成代码?",
JOptionPane.YES_NO_OPTION);
if (response == 0) {
GenerateHelper.generate(layers, tables);
JOptionPane.showMessageDialog(null,
"恭喜您,生成成功,详细信息见:" + GenerateHelper.daoPath + "\\src\\main\\resources\\history.txt");
}
}
});
JPanel linePanel3 = new JPanel();
linePanel3.setLayout(new BoxLayout(linePanel3, BoxLayout.X_AXIS));
linePanel3.add(new JLabel("其它功能:"));
JButton button1 = new JButton("删除刚生成的文件");
linePanel3.add(button1);
linePanel3.add(Box.createHorizontalStrut(500));
button1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int response = JOptionPane.showConfirmDialog(null, "您确定要删除刚才生成的代码?删除后将无法恢复!!", "确定删除?",
JOptionPane.YES_NO_OPTION);
if (response == 0) {
GenerateHelper.deleteFilesJustGenerated();
JOptionPane.showMessageDialog(null, "删除完毕!");
}
}
});
JPanel verticalPanel = new JPanel();
verticalPanel.setLayout(new BoxLayout(verticalPanel, BoxLayout.Y_AXIS));
verticalPanel.add(Box.createVerticalStrut(10));
verticalPanel.add(linePanel);
verticalPanel.add(Box.createVerticalStrut(10));
verticalPanel.add(linePanel2);
verticalPanel.add(Box.createVerticalStrut(20));
verticalPanel.add(linePanel3);
verticalPanel.add(Box.createVerticalStrut(20));
bottomPanel.add(verticalPanel);
}
public void updateTableList(String tableSchema) {
tablePanel.removeAll();
tableCheckBoxSelected.clear();
JdbcHelper.getAllTables(tableSchema).forEach(s -> {
JCheckBox checkBox = new JCheckBox(s);
checkBox.addActionListener(tableCheckBoxListener);
tablePanel.add(checkBox);
});
contentPanel.updateUI();
contentPanel.repaint();
}
public void updateTableListByFilter(String tableSchema, String filter) {
tablePanel.removeAll();
JdbcHelper.getTablesByFilter(tableSchema, filter).forEach(s -> {
Boolean selected = tableCheckBoxSelected.get(s);
if (selected == null)
selected = false;
JCheckBox checkBox = new JCheckBox(s, selected);
checkBox.addActionListener(tableCheckBoxListener);
tablePanel.add(checkBox);
});
contentPanel.updateUI();
contentPanel.repaint();
}
public Component getTableView() {
JTable jTable = new JTable();
return jTable;
}
public static void main(String[] args) {
new GenerateUI().getUI();
}
class BottomCheckBoxListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
JCheckBox checkBox = (JCheckBox) e.getSource();
if ("All".equals(checkBox.getText())) {
if (checkBox.isSelected()) {
mapperCheckBox.setSelected(true);
daoCheckBox.setSelected(true);
serviceCheckBox.setSelected(true);
controllerCheckBox.setSelected(true);
bottomCheckBoxSelected.put(mapperCheckBox.getText(), true);
bottomCheckBoxSelected.put(daoCheckBox.getText(), true);
bottomCheckBoxSelected.put(serviceCheckBox.getText(), true);
bottomCheckBoxSelected.put(controllerCheckBox.getText(), true);
} else {
mapperCheckBox.setSelected(false);
daoCheckBox.setSelected(false);
serviceCheckBox.setSelected(false);
controllerCheckBox.setSelected(false);
bottomCheckBoxSelected.put(mapperCheckBox.getText(), false);
bottomCheckBoxSelected.put(daoCheckBox.getText(), false);
bottomCheckBoxSelected.put(serviceCheckBox.getText(), false);
bottomCheckBoxSelected.put(controllerCheckBox.getText(), false);
}
return;
}
bottomCheckBoxSelected.put(checkBox.getText(), checkBox.isSelected());
}
}
class TableCheckBoxListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
JCheckBox checkBox = (JCheckBox) e.getSource();
tableCheckBoxSelected.put(checkBox.getText(), checkBox.isSelected());
}
}
}
41. 使用说明
(1)代码可以放在dao/service/controller随便哪个工程下,或放在另外一个与上述工程同级的工程下,我这里是放在spring-common工程下
(2)修改配置文件com.alan.generate.main.Constant.java
(3)运行com.alan.generate.main.Main.java
(4)点击schema后面的下拉列表选择相应的库
(5)可以通过filter筛选
(6)选择生成的层,可通过All选择所有层
(7)点击生成
(8)查看结果
(9)如果生成的类不想要了,可以点击最下方的按钮删除刚才生成的文件
(10)使用过程中有任何问题,请在下方评论,谢谢~~
八、自动生成接口文档
42. 思路
(1)首先要定义一个页面显示我们的接口文档,一般结构是左边的接口列表,右边是具体的接口信息,点击左右的接口,右边跟着变,类似下面这种结构:
(2)上述页面调用接口获取数据,假如叫/getApi;
(3)每个接口初始化的时候把自己的信息加到一个全局变量中保存起来,主要包括接口url、接口描述、接口参数、接口返回值等;
(4)调用/getApi时把上述全局变量中的数据返回;
(5)关键在于,怎么初始化的时候获取接口的信息?这个问题想到了就很简单,在父Controller的默认构造方法中通过反射获取子Controller的方法,然后解析出相应的信息即可,因为子类在初始化的时候会先调用父类的默认构造方法。
43. 限制与要求
(1)通过Java的反射无法获取参数的名称,所以我们的所有参数都需要封装在Model中;(通过javassist可以搞,不过我们这里直接这样定了,这样更明确)
(2)为了便于前端解析,所有的接口返回统一的Model,假如叫RestResult,包括code/message/result三个字段,其中result是Object类型,放入具体的返回值;
(3)有些参数是必填参数,添加@NotNull注解;
(4)需要显示接口描述、参数描述、返回值描述,添加@Description注解;
(5)有些接口不需要生成接口文档,添加@SuppressApi注解;
(6)具体的返回值放在了RestResult的result中,无法获取具体类型,添加@RestReturn指定具体的返回类型;
43. 代码实现
注:此处是用纯java写的,后面博主采用了freemarker模板来写,事半功倍,有兴趣的同学可以去学习一下。
生成接口文档相关代码放在alan-springmvc-common工程的com.alan.api包下。
(1)定义几个注解类型
新建包com.alan.api.annotation。
RestReturn.java
package com.alan.api.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 用于生成接口文档,标记返回类型
* @author Alan
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RestReturn {
Class<?> value();
boolean collection() default false;
}
NotNull.java
package com.alan.api.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NotNull {
}
Description.java
package com.alan.api.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Description {
String value() default "";
}
Suppress.java
package com.alan.api.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SuppressApi {
}
(2)定义几个Model
新建包com.alan.api.rest。
RestParam.java
package com.alan.api.rest;
/**
* 记录接口的参数信息
* @author Alan
*
*/
public class RestParam {
private String type;
private String name;
private String description;
private Boolean required = false;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Boolean getRequired() {
return required;
}
public void setRequired(Boolean required) {
this.required = required;
}
}
RestReturnParam.java
package com.alan.api.rest;
/**
* 接口的返回参数信息
* @author Alan
*
*/
public class RestReturnParam {
private String type;
private String name;
private String description;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
RestMethod.java
package com.alan.api.rest;
import java.util.ArrayList;
import java.util.List;
/**
* 用于记录每个接口的信息
* @author Alan
*
*/
public class RestMethod {
private String url;
private String description;
private List<RestParam> params = new ArrayList<>();
private List<RestReturnParam> returnParams = new ArrayList<>();
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public List<RestParam> getParams() {
return params;
}
public void addParam(RestParam param) {
this.params.add(param);
}
public List<RestReturnParam> getReturnParams() {
return returnParams;
}
public void addReturnParam(RestReturnParam returnParam) {
this.returnParams.add(returnParam);
}
}
RestResult.java
package com.alan.api.rest;
/**
* 统一的返回值
* @author Alan
*
*/
public class RestResult {
private Integer code;
private String message;
private Object result;
public RestResult() {
this.code = 0;
this.message = "success";
}
public RestResult(Object result) {
this();
this.result = result;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getResult() {
return result;
}
public void setResult(Object result) {
this.result = result;
}
}
(3)定义生成接口文档的父Controller
新建包com.alan.api.controller。
BaseApiController.java
package com.alan.api.controller;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import com.alan.api.annotation.Description;
import com.alan.api.annotation.NotNull;
import com.alan.api.annotation.RestReturn;
import com.alan.api.annotation.SuppressApi;
import com.alan.api.rest.RestMethod;
import com.alan.api.rest.RestParam;
import com.alan.api.rest.RestReturnParam;
import com.alan.generate.utils.Utils;
/**
* 用于生成接口文档的父Controller
* @author Alan
*
*/
public class BaseApiController {
private static final int LIST_REPEAT = 4;
private static final List<RestMethod> REST_LIST = new ArrayList<>();
public BaseApiController() {
REST_LIST.addAll(findRestMethod(this.getClass()));
}
public static List<RestMethod> getRestList() {
return REST_LIST;
}
private List<RestMethod> findRestMethod(Class<? extends BaseApiController> clazz) {
List<RestMethod> restList = new ArrayList<>();
String root = "/";
RequestMapping classRequestMapping = clazz.getAnnotation(RequestMapping.class);
if (classRequestMapping != null && classRequestMapping.value() != null) {
root = classRequestMapping.value()[0];
}
Method[] methods = clazz.getMethods();
for (Method method : methods) {
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
SuppressApi suppressApi = method.getAnnotation(SuppressApi.class);
if (requestMapping != null && suppressApi == null) {
RestMethod restMethod = new RestMethod();
if (requestMapping.value() != null) {
restMethod.setUrl((root + requestMapping.value()[0]).replaceAll("//", "/"));
}
Description description = method.getAnnotation(Description.class);
if (description != null) {
restMethod.setDescription(description.value());
}
setParams(restMethod, method);
setReturnParams(restMethod, method);
restList.add(restMethod);
}
}
return restList;
}
private boolean isTypeSkip(Class<?> clazz) {
return clazz == HttpServletRequest.class || clazz == HttpServletResponse.class || clazz == Model.class
|| clazz == ModelAndView.class;
}
private void setParams(RestMethod restMethod, Method method) {
Class<?>[] paramTypes = method.getParameterTypes();
// Parameter[] params = method.getParameters();
// Annotation[][] annotations = method.getParameterAnnotations();
for (int i = 0; i < paramTypes.length; i++) {
Class<?> paramType = paramTypes[i];
if(isTypeSkip(paramType)){
continue;
}
if (isBasicType(paramType)) {
throw new RuntimeException("You cannot set basic type as controller method parameter!");
// setBasicTypeParam(restMethod, paramType.getSimpleName(),
// params[i].getName(), annotations[i]);
} else {
setObjectTypeParam(restMethod, paramType, null);
}
}
}
private void setBasicTypeParam(RestMethod restMethod, String paramTypeName, String paramName,
Annotation[] annotations) {
RestParam restParam = new RestParam();
restParam.setName(paramName);
restParam.setType(paramTypeName);
if (annotations != null && annotations.length > 0) {
for (Annotation annotation : annotations) {
if (annotation instanceof NotNull) {
restParam.setRequired(true);
continue;
}
if (annotation instanceof Description) {
restParam.setDescription(((Description) annotation).value());
continue;
}
}
}
restMethod.addParam(restParam);
}
private void setObjectTypeParam(RestMethod restMethod, Class<?> paramType, String prefix) {
Field[] fields = paramType.getDeclaredFields();
for (Field field : fields) {
// 字段的修饰符,default: 0, public: 1, private: 2, static: 8, final: 16
// 这样可以排除掉static或final的字段
if ((field.getModifiers() & 8) == 0) {
if (isBasicType(field.getType())) {
setBasicTypeParam(restMethod, field.getType().getSimpleName(),
Utils.isEmpty(prefix) ? field.getName() : prefix + "." + field.getName(),
field.getAnnotations());
} else if (List.class.isAssignableFrom(field.getType())) {
Class<?> listActualType = getCollectionFieldActualType(field);
String name = Utils.isEmpty(prefix) ? field.getName() : prefix + "." + field.getName();
if (isBasicType(listActualType)) {
for (int i = 0; i < LIST_REPEAT; i++) {
setBasicTypeParam(restMethod, listActualType.getSimpleName(), name + "[" + i + "]",
field.getAnnotations());
}
} else {
for (int i = 0; i < LIST_REPEAT; i++) {
setObjectTypeParam(restMethod, listActualType, name + "[" + i + "]");
}
}
} else {
setObjectTypeParam(restMethod, field.getType(),
Utils.isEmpty(prefix) ? field.getName() : prefix + "." + field.getName());
}
}
}
}
private void setReturnParams(RestMethod restMethod, Method method) {
RestReturn restReturn = method.getAnnotation(RestReturn.class);
boolean isCollection = restReturn.collection();
Class<?> returnType = restReturn.value();
if (isBasicType(returnType)) {
throw new RuntimeException("You cannot return basic type!");
} else {
if (isCollection) {
setCollectionReturnParam(restMethod, returnType, " ");
} else {
setObjectReturnParam(restMethod, returnType, " ");
}
}
}
private void setBasicReturnParam(RestMethod restMethod, String returnParamTypeName, String returnParamName,
Annotation[] annotations) {
RestReturnParam returnParam = new RestReturnParam();
returnParam.setName(returnParamName);
returnParam.setType(returnParamTypeName);
if (annotations != null && annotations.length > 0) {
for (Annotation annotation : annotations) {
if (annotation instanceof Description) {
returnParam.setDescription(((Description) annotation).value());
break;
}
}
}
restMethod.addReturnParam(returnParam);
}
private void setObjectReturnParam(RestMethod restMethod, Class<?> returnType, String prefix) {
Field[] fields = returnType.getDeclaredFields();
for (Field field : fields) {
if ((field.getModifiers() & 8) == 0) {
if (isBasicType(field.getType())) {
setBasicReturnParam(restMethod, field.getType().getSimpleName(),
Utils.isEmpty(prefix) ? field.getName() : prefix + field.getName(), field.getAnnotations());
} else if (List.class.isAssignableFrom(field.getType())) {
setBasicReturnParam(restMethod, field.getType().getSimpleName(),
Utils.isEmpty(prefix) ? field.getName() : prefix + field.getName(), field.getAnnotations());
Class<?> listActualType = getCollectionFieldActualType(field);
prefix = " " + prefix;
addStartCollectionReturnParam(restMethod, prefix);
if (!isBasicType(listActualType)) {
setObjectReturnParam(restMethod, listActualType, prefix);
}
addEndCollectionReturnParam(restMethod, prefix);
}
}
}
}
private void setCollectionReturnParam(RestMethod restMethod, Class<?> returnType, String prefix) {
addStartCollectionReturnParam(restMethod, prefix);
setObjectReturnParam(restMethod, returnType, prefix);
addEndCollectionReturnParam(restMethod, prefix);
}
private void addStartCollectionReturnParam(RestMethod restMethod, String prefix) {
setBasicReturnParam(restMethod, null, Utils.isEmpty(prefix) ? "[" : prefix + "[", null);
}
private void addEndCollectionReturnParam(RestMethod restMethod, String prefix) {
setBasicReturnParam(restMethod, null, Utils.isEmpty(prefix) ? "]" : prefix + "]", null);
}
private Class<?> getCollectionFieldActualType(Field field) {
Type genType = field.getGenericType();
if (genType instanceof ParameterizedType) {
((ParameterizedType) genType).getActualTypeArguments();
Type[] parameterizedTypes = ((ParameterizedType) genType).getActualTypeArguments();
if (parameterizedTypes.length > 0) {
return ((Class<?>) parameterizedTypes[0]);
} else {
throw new RuntimeException("All List type must set its generic type!");
}
} else {
throw new RuntimeException("All List type must set its generic type!");
}
}
private boolean isBasicType(Class<?> paramType) {
return paramType.isPrimitive() || paramType == String.class || Number.class.isAssignableFrom(paramType)
|| Date.class.isAssignableFrom(paramType) || Timestamp.class.isAssignableFrom(paramType)
|| Enum.class.isAssignableFrom(paramType);
}
}
(4)新建获取接口文档的Controller,其继承自BaseApiController,并写几个测试的接口
位于alan-springmvc-web工程的com.alan.controller包下。
ApiController.java
package com.alan.controller;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.alan.api.annotation.Description;
import com.alan.api.annotation.RestReturn;
import com.alan.api.annotation.SuppressApi;
import com.alan.api.controller.BaseApiController;
import com.alan.api.rest.RestResult;
import com.alan.entity.UserInfo;
import com.alan.entity.UserTestXyz;
@Controller
public class ApiController extends BaseApiController {
@SuppressApi
@RequestMapping("/api")
public String api() {
return "api-index.html";
}
@SuppressApi
@RequestMapping("/apiList")
@ResponseBody
public RestResult apiList(HttpServletRequest request) {
String contextPath = request.getContextPath();
RestResult restResult = new RestResult(BaseApiController.getRestList());
restResult.setMessage(contextPath);
return restResult;
}
@RequestMapping("/test")
@ResponseBody
@Description("测试")
@RestReturn(UserInfo.class)
public RestResult test(UserInfo userInfo){
return new RestResult(userInfo);
}
@RequestMapping("/test2")
@ResponseBody
@Description("测试2")
@RestReturn(value=UserTestXyz.class,collection=true)
public RestResult test2(UserTestXyz userTestXyz){
List<UserTestXyz> list = new ArrayList<>();
list.add(userTestXyz);
return new RestResult(list);
}
}
(5)编写页面及javascript
api-index.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>接口文档</title>
<style type="text/css">
#left {
border: 1px solid red;
width: 25%;
float: left;
}
#right {
border: 1px solid green;
width: 74%;
float: right;
padding-top: 50px;
}
#result {
border: 1px solid blue;
width: 500px;
margin-left: 50px;
text-align: left;
padding: 10px 10px 40px 10px;
}
table {
border: solid #add9c0;
border-width: 1px 0px 0px 1px;
width: 500px;
margin-left: 50px;
}
td, th {
border: solid #add9c0;
border-width: 0px 1px 1px 0px;
padding: 10px 0px;
}
</style>
<body>
<div id="left"></div>
<div id="right">
<form action="/api" onsubmit="return false;" id="form">
<table id="param-list">
</table>
<br />
<input type="submit" value="提交" id="submit" style="margin-left: 350px;">
<br />
<br />
<table id="return-list">
</table>
</form>
<div id="result"></div>
</div>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="api-index.js"></script>
</body>
</html>
api-index.js
var apiIndex = {
apiList: null,
nowUrl: null,
contextPath: null,
getApiList: function(){
var apiListUrl = "/alan-springmvc-web/apiList";
$.post(apiListUrl, null, function(data){
if(data && data.result && data.result.length){
apiIndex.apiList = data.result;
apiIndex.contextPath = data.message;
var html = "";
for(var i=0; i<apiIndex.apiList.length; i++){
html += "<p class='item' data-index="+i+"><a href='#'>"+apiIndex.apiList[i].url+"</a><span> "+apiIndex.apiList[i].description+"</span></p>";
}
$("#left").html(html);
$(".item").click(function(){
apiIndex.getApiDetail($(this).attr("data-index"));
});
apiIndex.getApiDetail(0);
}
},"json");
},
getApiDetail: function(index){
if(this.apiList && this.apiList.length){
$("#result").text("");
$("#param-list").html('<tr>\
<td colspan="4" id="api-name"></td>\
</tr>\
<tr>\
<td colspan="4"><b>参数列表</b></td>\
</tr>\
<tr>\
<th>名称</th>\
<th>值</th>\
<th>类型</th>\
<th>描述</th>\
</tr>');
$("#return-list").html('<tr>\
<td colspan="3"><b>返回值列表</b></td>\
</tr>\
<tr>\
<th>名称</th>\
<th>类型</th>\
<th>描述</th>\
</tr>\
<tr>\
<td>code</td>\
<td>Integer</td>\
<td>返回代码,默认为0</td>\
</tr>\
<tr>\
<td>message</td>\
<td>String</td>\
<td>返回消息,默认为success</td>\
</tr>\
<tr>\
<td>result</td>\
<td></td>\
<td>返回结果的实际值</td>\
</tr>');
var html = "";
var paramTemplate = "<tr>\
<td>#{name}</td>\
<td><input type='text' name='#{name}' /></td>\
<td>#{type}</td>\
<td>#{description}</td>\
</tr>";
var paramRequiredTemplate = "<tr>\
<td><span style='color:red;'>#{name}</span></td>\
<td><input type='text' name='#{name}' /></td>\
<td>#{type}</td>\
<td>#{description}</td>\
</tr>";
var paramList = this.apiList[index].params;
this.nowUrl = this.apiList[index].url;
$("#api-name").text(this.nowUrl);
for(var i=0; i<paramList.length; i++){
var param = paramList[i];
var content = null;
if(param.required){
content = paramRequiredTemplate;
}else{
content = paramTemplate;
}
var content = content.replace(/#{name}/g,param.name?param.name:"");
var content = content.replace(/#{type}/,param.type?param.type:"");
var content = content.replace(/#{description}/,param.description?param.description:"");
html += content;
}
$("#param-list").append(html);
var returnTemplate = "<tr>\
<td>#{name}</td>\
<td>#{type}</td>\
<td>#{description}</td>\
</tr>";
html = "";
var returnList = this.apiList[index].returnParams;
for(var i=0; i<returnList.length; i++){
var returnParam = returnList[i];
var content = returnTemplate.replace(/#{name}/,returnParam.name?returnParam.name:"");
var content = content.replace(/#{type}/,returnParam.type?returnParam.type:"");
var content = content.replace(/#{description}/,returnParam.description?returnParam.description:"");
html += content;
}
$("#return-list").append(html);
}
}
}
$(function(){
apiIndex.getApiList();
$("#submit").click(function(){
var param = {};
var inputs = $("#form input");
for(var i=0; i<inputs.length; i++){
if(inputs[i].type=="text"){
param[inputs[i].name] = inputs[i].value;
}
}
$.post(apiIndex.contextPath+apiIndex.nowUrl,param,function(data){
$("#result").text(data);
},'text');
});
});
44. 效果展示
(1)启动tomcat,访问http://localhost:8080/alan-springmvc-web/api
(2)输入一些参数,点提交
(3)查看效果
45. 总结
(1)接口的参数无法支持基本类型(包括String),这是因为Java本身反射无法取得参数名称,可通过javassit解决,有兴趣的同学自己解决下;
(2)统一返回类型为RestResult;
(3)页面上红色的参数表示必填参数,目前未实现必填参数不填报错,可通过拦截器统一处理;
(4)我们自己的写的参数Model和返回值Model可直接拿给Android使用,重新修改Model直接重新给他们一份即可,减少了双方因接口沟通的时间,这也是接口参数不支持基本类型的原因之一,IOS的Model也可以根据脚本自动生成一份对应的Model;
(5)可直接在页面上填写参数进行调试,非常方便;
(6)可以让BaseController继承自BaseApiController,使原有接口支持生成接口文档,不过一些通用方法可能要修改。
(7)局限性,要写好多Model,每个接口都要定义一个方法,有点麻烦,不过结构更清晰;
九、配置redis
十、配置dubbo
十一、配置RabbitMQ
十二、配置多数据库
~~~~~~~~~~~未完待续~~~~~~~~~~~~~~~~~~~~~~~