学任何东西,做任何事情,都得先有原因,否则没有意义,就比如各种框架存在的意义。现在有一种经常遇到的情况,我们经常需要有一个独立的程序来处理一些业务逻辑,也就是小程序(此小程序非微信小程序)。这种小程序有很多,并且每一个都是独立运行,处理独立的业务逻辑,比如定时处理订单的明细数据,定时推送什么的。这种情况下小程序的要求很简单,可以连接数据库即可。
这里用Java小程序来实现,目的是可以连接数据库,控制事物即可,当然后续连接redis、rocketmq也是可以的。那么当然会想得到用spring控制事物,mybatis来操作数据库,数据库连接池用阿里的druid。
我们通常会遇到一个问题,那就是这种小程序打包成jar包后,进程会把一些配置文件,比如数据库链接,自定义的配置文件都打包到jar包里面,但是开发、测试、生产这些东西都是不同的,岂不是我们每次打包之前都要改动配置文件?这样子也太不科学了吧。
目的
搭建一个java小程序模板(可执行jar),用spring管理事务,mybatis操作数据库,用阿里的连接池druid右键run将该maven打包成一个可执行jar,包括spring依赖都打包成一个jar
项目结构

数据库链接信息以及自定义的配置文件都放在jar包外面也就是app.properties
1、pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.gdpost</groupId><artifactId>app-template</artifactId><version>0.0.1-SNAPSHOT</version><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><spring.version>5.1.6.RELEASE</spring.version></properties><dependencies><!--Spring框架核心库 --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>${spring.version}</version></dependency><!-- spring整合oracle --><dependency><groupId>com.oracle</groupId><artifactId>ojdbc6</artifactId><version>12.1.0.1-atlassian-hosted</version></dependency><!-- spring整合mysql,和上面不冲突 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.38</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.12</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.4.1</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>1.3.0</version></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency></dependencies><build><plugins><plugin><artifactId>maven-compiler-plugin</artifactId><configuration><source>1.6</source><target>1.6</target></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><version>1.4</version><configuration><createDependencyReducedPom>false</createDependencyReducedPom></configuration><executions><execution><!-- 执行package的phase --><phase>package</phase><!-- 为这个phase绑定goal --><goals><goal>shade</goal></goals><configuration><!-- 过滤掉以下文件,不打包 :解决包重复引用导致的打包错误--><filters><filter><artifact>*:*</artifact><excludes><exclude>META-INF/*.SF</exclude><exclude>META-INF/*.DSA</exclude><exclude>META-INF/*.RSA</exclude></excludes></filter></filters><transformers><transformerimplementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"><resource>META-INF/spring.handlers</resource></transformer><!-- 打成可执行的jar包 的主方法入口--><transformerimplementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"><mainClass>com.gdpost.App</mainClass></transformer><transformerimplementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"><resource>META-INF/spring.schemas</resource></transformer></transformers></configuration></execution></executions></plugin></plugins><defaultGoal>compile</defaultGoal></build></project>
这里用的是最新版的spring,以及用mybatis来操作数据库,要注意的是build中的内容,这里使用maven-shade-plugin插件将项目打成可执行的jar包,因为有第三方依赖包spring所以用这种方法可以把所有jar打包成一个可执行jar,当然用其他插件也是可以的。打包方式是maven-build 然后输入pacakege即可。
2、spring-mybatis.xml
<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:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-4.3.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-4.3.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-4.3.xsd"><!-- 自动扫描 --><context:component-scan base-package="com.gdpost" /><!-- 引入配置文件:不需要 --><!-- <bean id="propertyConfigurer"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"><property name="location" value="classpath:jdbc.properties" /></bean> --><!-- <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"destroy-method="close"><property name="driverClassName" value="${driver}" /><property name="url" value="${url}" /><property name="username" value="${username}" /><property name="password" value="${password}" /></bean> --><!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 --><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="dataSource" /><!-- 自动扫描mapping.xml文件 --><property name="mapperLocations" value="classpath:mapper/*.xml"></property></bean><!-- DAO接口所在包名,Spring会自动查找其下的类 --><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="com.gdpost.mapper" /><property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property></bean><!-- (事务管理)transaction manager, use JtaTransactionManager for global tx --><bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource" /></bean><!--支持注解驱动的事务管理,指定事务管理器 --><tx:annotation-driven transaction-manager="transactionManager"/></beans>
这个配置文件是最重要的配置文件,上面的内容都是必须的,我这里注释掉了数据库连接池对象的生成,因为我把数据库链接的配置信息移动到了外部,所以我这里直接用配置类来生成,如下:
3、DruidDataSourceConfiguration.java
@Configurationpublic class DruidDataSourceConfiguration {private String dbUrl = PropertiesUtil.get("dataSource.url");private String username =PropertiesUtil.get("dataSource.username");private String password =PropertiesUtil.get("dataSource.password");private String driverClassName =PropertiesUtil.get("dataSource.driver");private int initialSize=Integer.parseInt(PropertiesUtil.get("dataSource.initialSize","5"));private int minIdle= Integer.parseInt(PropertiesUtil.get("dataSource.minIdle","5"));private int maxActive= Integer.parseInt(PropertiesUtil.get("dataSource.maxActive","20"));private int maxWait= Integer.parseInt(PropertiesUtil.get("dataSource.maxWait","60000"));private int timeBetweenEvictionRunsMillis= Integer.parseInt(PropertiesUtil.get("dataSource.timeBetweenEvictionRunsMillis","60000"));private int minEvictableIdleTimeMillis= Integer.parseInt(PropertiesUtil.get("dataSource.minEvictableIdleTimeMillis","300000"));private String validationQuery= PropertiesUtil.get("dataSource.validationQuery","SELECT 1 FROM DUAL");private boolean testWhileIdle= Boolean.parseBoolean(PropertiesUtil.get("dataSource.testWhileIdle","true"));private boolean testOnBorrow= Boolean.parseBoolean(PropertiesUtil.get("dataSource.testOnBorrow","false"));private boolean testOnReturn= Boolean.parseBoolean(PropertiesUtil.get("dataSource.testOnReturn","false"));private boolean poolPreparedStatements=Boolean.parseBoolean(PropertiesUtil.get("dataSource.poolPreparedStatements","true"));private int maxPoolPreparedStatementPerConnectionSize= Integer.parseInt(PropertiesUtil.get("dataSource.maxPoolPreparedStatementPerConnectionSize","20"));//wall, 去掉这个,不然会报sql injection violationprivate String filters= PropertiesUtil.get("dataSource.filters","stat,log4j");private String connectionProperties= PropertiesUtil.get("dataSource.connectionProperties","druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000");@Bean //声明其为Bean实例@Primary //在同样的DataSource中,首先使用被标注的DataSourcepublic DataSource dataSource() {DruidDataSource datasource = new DruidDataSource();datasource.setUrl(this.dbUrl);datasource.setUsername(username);datasource.setPassword(password);datasource.setDriverClassName(driverClassName);//configurationdatasource.setInitialSize(initialSize);datasource.setMinIdle(minIdle);datasource.setMaxActive(maxActive);datasource.setMaxWait(maxWait);datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);datasource.setValidationQuery(validationQuery);datasource.setTestWhileIdle(testWhileIdle);datasource.setTestOnBorrow(testOnBorrow);datasource.setTestOnReturn(testOnReturn);datasource.setPoolPreparedStatements(poolPreparedStatements);datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);try {datasource.setFilters(filters);} catch (SQLException e) {e.printStackTrace();}datasource.setConnectionProperties(connectionProperties);return datasource;}}
@Configuration 表示这个java类是一个配置类,下面的dataSource就对应配置文件中注释掉的dataSource,这样子就不用从classpath下面获取链接信息啦,直接通过配置文件工具类PropertiesUtil从app.properties中获取,下面我们来看一下配置文件工具类怎么实现的。
4、PropertiesUtil.java
/*** 用完美的单例模式获取properties的数据,加载为map* @author forever**/public class PropertiesUtil {//值可变,引用不可变private static final Properties PROPERTIES =new Properties();static{initProperties();}/*** @param key* @param defaultValue* @return*/public static String get(String key,String defaultValue){if(PROPERTIES.isEmpty()) {throw new UnsupportedOperationException("配置未加载");}String value =PROPERTIES.getProperty(key, defaultValue);return value;}/*** 获取默认配置的值,这个值可以动态修改* @param key* @return*/public static String get(String key){return get(key, "");}private static void initProperties(){InputStream is =null;try {//获取当前目录String property = System.getProperty("user.dir");//默认是linux osString fileName = "/app.properties";//判断是否是windows osif(System.getProperty ("os.name").contains("Windows")) {fileName = "\\app.properties";}// 读取当前目录下conf配置文件File file = new File(property+fileName);PROPERTIES.clear();is = new FileInputStream(file);PROPERTIES.load(is);}catch (IOException e) {e.printStackTrace();}finally {if(is!=null) {try {is.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}}
上面巧妙的用了这个方法
String property = System.getProperty("user.dir");
来获取当前目录,这样子配置文件就跟jar包分开来啦,完美,并且在static静态代码块中来初始化配置类,这样子在第一次使用的时候就可以初始化了,然后我们来看一下我们的配置类:
5、app.properties
###数据库链接dataSource.driver=com.mysql.jdbc.DriverdataSource.url=jdbc:mysql://localhost:3306/weixinserdataSource.username=rootdataSource.password=forever###连接池配置信息(有默认配置)#dataSource.initialSize=5#dataSource.minIdle=5#dataSource.maxActive=20#dataSource.maxWait=60000#dataSource.timeBetweenEvictionRunsMillis=60000#dataSource.minEvictableIdleTimeMillis=300000#dataSource.validationQuery=SELECT 1 FROM DUAL#dataSource.testWhileIdle=true#dataSource.testOnBorrow=false#dataSource.testOnReturn=false#dataSource.poolPreparedStatements=true#dataSource.maxPoolPreparedStatementPerConnectionSize=20###wall, 去掉这个,不然会报sql injection violation#dataSource.filters=stat,log4j#dataSource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000###下面是自定义配置username=lwh
上面的数据库链接只要换成oracle就对应oracle的数据库,并且链接池信息有默认的,修改的话就放开对应的注释即可。然后自定义配置就写在后面,获取配置信息的方法就是PropertiesUtil.get("username");我们再看下我们的最重要的SpringUtil
6、SpringUtil.java
public class SpringUtil {private static ApplicationContext context = new ClassPathXmlApplicationContext("spring-mybatis.xml");public static Object getBean(String serviceName){return context.getBean(serviceName);}}
这里是在开始用的时候就初始化了spring容器,然后就直接使用啦。
后面我们再列一下业务逻辑要使用的类:
7、User.java、UserMappper.java、UserMapper.xml、UserService.java、UserServiceImpl.java
User.java
public class User{private Long id;//自增的IDprivate String username;//用户名private String userId;//这个用来存放用户的userId,不要用自增键太不安全private String password;//用户密码private String nickname;//昵称private String imgUrl;//用户头像private String sex;//用户性别0女,1是男private String age;//用户年龄private String create_datetime;//用户创建时间private String update_datetime;//用户修改时间private String type;//用户类别0是管理员,1是已登录用户,2是游客private String remark;//用户备注private String visible;//用户是否有效private String value;//预留字段public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getUserId() {return userId;}public void setUserId(String userId) {this.userId = userId;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getNickname() {return nickname;}public void setNickname(String nickname) {this.nickname = nickname;}public String getImgUrl() {return imgUrl;}public void setImgUrl(String imgUrl) {this.imgUrl = imgUrl;}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}public String getAge() {return age;}public void setAge(String age) {this.age = age;}public String getCreate_datetime() {return create_datetime;}public void setCreate_datetime(String createDatetime) {create_datetime = createDatetime;}public String getUpdate_datetime() {return update_datetime;}public void setUpdate_datetime(String updateDatetime) {update_datetime = updateDatetime;}public String getType() {return type;}public void setType(String type) {this.type = type;}public String getRemark() {return remark;}public void setRemark(String remark) {this.remark = remark;}public String getValue() {return value;}public void setValue(String value) {this.value = value;}public String getVisible() {return visible;}public void setVisible(String visible) {this.visible = visible;}@Overridepublic String toString() {return "User [id=" + id + ", username=" + username + ", userId="+ userId + ", password=" + password + ", nickname=" + nickname+ ", imgUrl=" + imgUrl + ", sex=" + sex + ", age=" + age+ ", create_datetime=" + create_datetime + ", update_datetime="+ update_datetime + ", type=" + type + ", remark=" + remark+ ", visible=" + visible + ", value=" + value + "]";}
UserMappper.java
public interface UserMapper {public List<com.gdpost.model.User> findAllUser();public void insertUser(User user);}
UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.gdpost.mapper.UserMapper"><!--模板--><select id="findAllUser" resultType="com.gdpost.model.User">select * from user</select><insert id="insertUser" parameterType="com.gdpost.model.User">insert into user(id,username,password,nickname,imgUrl,sex,age,create_datetime,update_datetime,type,remark,visible,value,userId)values(#{id},#{username},#{password},#{nickname},#{imgUrl},#{sex},#{age},#{create_datetime},#{update_datetime},#{type},#{remark},#{visible},#{value},#{userId})</insert></mapper>
UserService.java
public interface UserService {public List<User> findAllUser();public void insertUser();}
UserServiceImpl.java
@Service("userService")public class UserServiceImpl implements UserService{@Autowiredprivate UserMapper userMapper;public List<User> findAllUser() {List<User> users = userMapper.findAllUser();return users;}//事务控制成功@Transactionalpublic void insertUser() {User user1 = new User();user1.setId(1l);user1.setUsername("1");user1.setPassword("1");user1.setUserId("1");userMapper.insertUser(user1);int i=1/0;User user2 = new User();user2.setId(2l);user2.setUsername("2"+i);user2.setPassword("2");user2.setUserId("2");userMapper.insertUser(user2);}}
insertUser方法是测试事务,经过测试,事务可以回滚。
8、App.java
public class App {private static final Logger log = Logger.getLogger(App.class);public static void main(String[] args) {UserService userService =(UserService) SpringUtil.getBean("userService");List<User> users = userService.findAllUser();log.info("user:"+users);userService.insertUser();}}
这个是启动类,直接使用即可。
GITHUB地址
https://github.com/suibibk/app-template.git
总结
以后可以直接用了,美滋滋
