今天整合spring和mybatis时,在通过@Autowired注入Dao时一直报

1
expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

查看了很久applicationContext.xml中的配置没有找出问题,最后对照以前项目的web.xml配置时发现少了一个:
1
2
3
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

加上后问题解决。

ContextLoaderListener通过读取contextConfigLocation参数来读取配置参数,一般来说它配置的是Spring项目的中间层,服务层组件的注入,装配,AOP。

pom.xml增加以下依赖:

pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<properties>
......

<jdbc.driver.groupId>postgresql</jdbc.driver.groupId>
<jdbc.driver.artifactId>postgresql</jdbc.driver.artifactId>
<jdbc.driver.version>9.1-901.jdbc4</jdbc.driver.version>
</properties>

<dependencies>
......

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>

<!-- connection pool -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
<version>${tomcat-jdbc.version}</version>
<scope>runtime</scope>
</dependency>

<!-- jdbc driver -->
<dependency>
<groupId>${jdbc.driver.groupId}</groupId>
<artifactId>${jdbc.driver.artifactId}</artifactId>
<version>${jdbc.driver.version}</version>
<scope>runtime</scope>
</dependency>

<!-- mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.3.0</version>
</dependency>

<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.2</version>
</dependency>
</dependencies>

在applicationContext.xml中增加以下配置,其中${jdbc…}配置在application.properties中

applicationContext.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
......

<context:property-placeholder ignore-unresolvable="true" location="classpath*:/application.properties" />

<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

<!-- 创建SqlSessionFactory,同时指定数据源-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- 自动扫描entity目录, 省掉Configuration.xml里的手工配置 -->
<property name="typeAliasesPackage" value="com.climbran.spring.entity" />
<!-- 显式指定Mapper文件位置 -->
<property name="mapperLocations" value="classpath:/mybatis/*Mapper.xml" />
</bean>

<!-- 扫描basePackage下所有以@MyBatisRepository标识的 接口-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.climbran.spring.repository" />
<property name="annotationClass" value="com.climbran.spring.repository.MyBatisRepository"/>
</bean>

<!-- 数据源配置, 使用Tomcat JDBC连接池 -->
<bean id="dataSource" class="org.apache.tomcat.jdbc.pool.DataSource" destroy-method="close">
<!-- Connection Info -->
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />

<!-- Connection Pooling Info -->
<property name="maxActive" value="${jdbc.pool.maxActive}" />
<property name="maxIdle" value="${jdbc.pool.maxIdle}" />
<property name="minIdle" value="0" />
<property name="defaultAutoCommit" value="false" />
</bean>

在src/main/resources下新建application.properties文件:

application.properties
1
2
3
4
5
6
jdbc.driver=org.postgresql.Driver
jdbc.url=jdbc:postgresql://127.0.0.1:5432/test
jdbc.username=数据库用户名
jdbc.password=数据库密码
jdbc.pool.maxIdle=10
jdbc.pool.maxActive=50

applicationContext.xml文件中,SqlSessionFactory的typeAliasesPackage配置entity的扫描目录,mapperLocations配置mybatis映射文件,示例如下:

User.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.climbran.spring.entity;

/**
* @author climbran
*/

public class User {
private Integer id;
private String username;
private String schoolName;

public Integer getId(){
return id;
}
public void setId(Integer id){
this.id = id;
}

public String getUsername(){
return username;
}

public void setUsername(String username){
this.username = username;
}

public String getSchoolName(){
return schoolName;
}

public void setSchoolNamer(String schoolName){
this.schoolName = schoolName;
}
}

在src/main/resources下新建文件夹mybatis,再在mybatis下新建文件UserMapper.xml

UserMapper.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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">

<!-- namespace必须指向Dao接口 -->
<mapper namespace="com.climbran.spring.repository.UserDao">
<!--
获取用户: 输出直接映射到对象, school_name列要"as schoolName"以方便映射
-->

<select id="get" parameterType="integer" resultType="User">
select id, username,
school_name as schoolName
from public.user
where id=#{id}
</select>

</mapper>

以前使用的JPA貌似public schema下载@Table注解中public可以省略,不过mybatis测试时from语句不写public会报错BadSqlGrammarException

applicationContext.xml中org.mybatis.spring.mapper.MapperScannerConfigurer配置了dao层文件所在位置,所以根据UserMapper.xml中的配置,对应的java文件为:

UserDao.java
1
2
3
4
5
6
7
8
9
10
11
package com.climbran.spring.repository;

import com.climbran.spring.entity.User;

/**
* @author climbran
*/

@MyBatisRepository
public interface UserDao {

User get(Integer id);
}

org.mybatis.spring.mapper.MapperScannerConfigurer的annotationClass配置是表示已注解方式扫描dao,自动注册有value值对应注解的bean,例如上面UserDao的@MyBatisRepository注解,其是实现如下:

UserDao.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.climbran.spring.repository;

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;
import org.springframework.stereotype.Component;

/**
* 标识MyBatis的DAO,方便{@link org.mybatis.spring.mapper.MapperScannerConfigurer}的扫描。
*
* @author climbran
*
*/

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Component
public @interface MyBatisRepository {

String value() default "";
}

使用时在Service中通过@Autowired注入UserDao即可访问数据库。
最后是UserMapper.xml映射的表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- Table: "user"

-- DROP TABLE "user";

CREATE TABLE "user"
(
id serial NOT NULL,
username character varying,
school_name character varying,
CONSTRAINT user_pkey PRIMARY KEY (id)
)
WITH (
OIDS=FALSE
);

ALTER TABLE "user"
OWNER TO postgres;

使用hexo后一直用<pre>加<code>标签表示代码块,但是一遇到’<’符号就出先各种奇怪错误,还每次情况都不一样,今天在一篇文档发现正确使用方式:

1
2
3
{ %codeblock [title] [lang:language] [url] [link text] % }
code snippet
{ % endcodeblock % }

注意要去掉花括号与%之间的空格

今天下载了个intelliJ试着用下。官网有30天试用的Ultimate版本和免费的Community版本,先下载了Community Editor,结果发现没有application service功能,无法使用tomcat,果断换Ultimate Editor,找了个激活码激活。

下载链接:http://www.jetbrains.com/idea/


下载安装打开,界面风格类似rubyMine,自从用过rubyMine之后一直嫌弃eclipse的界面,看着这个感觉爽多了。


接下来开始试着创建Spring项目,先新建一个new project,左边选择spring,右边勾选spring mvc:


点击next,依次按提示完成创建


在项目上右键,选择open modules setting,参照以前eclipse下的spring项目建立以下目录结构:




然后根据个人习惯修改配置,我的配置如下:

web.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">


<display-name>spring-web</display-name>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:/applicationContext.xml
</param-value>
</context-param>

<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>

<servlet>
<servlet-name>springServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>



web.xml配置了applicationContext.xml和spring-mvc.xml两个文件,在src/main/resources目录下新建applicationContext.xml:
applicationContext.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd "

default-lazy-init="true">

//your setting
</beans>




将src/main/webapp/WEB-INF目录下除web.xml的另一个xml文件rename为spring-mvc.xml(对应web.xml中DispatcherServlet的配置,不填默认为[servelet-name]-servlet.xml):
spring-mvc.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">


<context:component-scan base-package="com.climbran.webdemo.spring" />
<mvc:default-servlet-handler/>

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
<bean>

<mvc:annotation-driven/>

</beans>




再src/main/webapp/WEB-INF目录下新建pages文件夹rename为view(对应spring-mvc.xml文件中的配置)

之前尝试折腾过octpress,不过使用起来实在感觉麻烦,今天发现hexo,尝试了一下感觉方便很多,需要安装node.js,基本根据下面两篇文档就能搞定:

hexo文档

如何使用Jacman主题

两个项目作者一个是台湾大学生,一个是北理大学生,膜拜一下


配置起来很顺利,就是在配置category和tags插件的时候要生成带有categories和tags的文章后插件才能显示出来,配置好后以为没成功折腾了好一会。


配置好后将源文件上传github,方便在其他地方使用。其他电脑上clone下来后需先配置好环境,再在工程目录下运行npm install。

———–2015.12.4更新—————

使用hexo-generator-baidu-sitemap生成sitemap,在github上clone后需要在工程目录下运行

1
$ npm install hexo-generator-baidu-sitemap@0.1.1 --save

UserController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package org.springside.examples.showcase.web;

import java.util.List;
......

import com.google.common.collect.Maps;

@Controller
@RequestMapping(value = "/account/user")
public class UserController {

......

@RequestMapping(value = "update", method = RequestMethod.POST)
public String update(@Valid @ModelAttribute("user") User user,
@RequestParam(value = "roleList") List<Long> checkedRoleList, RedirectAttributes redirectAttributes) {


// bind roleList
user.getRoleList().clear();
for (Long roleId : checkedRoleList) {
Role role = new Role(roleId);
user.getRoleList().add(role);
}

accountService.saveUser(user);

redirectAttributes.addFlashAttribute("message", "保存用户成功");
return "redirect:/account/user";
}

/**
* 所有RequestMapping方法调用前的Model准备方法, 实现Struts2 Preparable二次部分绑定的效果,先根据form的id从数据库查出User对象,再把Form提交的内容绑定到该对象上。
* 因为仅update()方法的form中有id属性,因此仅在update时实际执行.
*/

@ModelAttribute
public void getUser(@RequestParam(value = "id", defaultValue = "-1") Long id, Model model) {
if (id != -1) {
model.addAttribute("user", accountService.getUser(id));
}
}

/**
* User类中有roleList,不自动绑定对象中的roleList属性,另行处理。
*/

@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setDisallowedFields("roleList");
}
}

其中getUser方法可写成

1
2
3
4
5
@ModelAttribute
public User getUser(@RequestParam(value = "id", defaultValue = "-1") Long id, Model model) {
if (id != -1)
return accountService.getUser(id);
}

这种情况,函数隐含Model类型的参数,会调用addAttribute方法,addAttribute的key没有指定,它由返回类型隐含表示,如这个方法返回User类型,那么这个key是user,value为返回对象,其中key值也可以通过@ModelAttribute注解的value参数指定(eg:@ModelAttribute(“user”))。



update方法如果没有标注@SessionAttributes(“user”),那么scope为request,如果标注了,那么scope为session。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
interface Iterator{
boolean end();
Object current();
void next();
}

public class Sequence{
private Object[] items;
private next = 0;

public Sequence(int size){
items = new Objec[size];
}

public void add(Object x){
if(next&ltitems.length)
items[next++] = x;
}

private class SequenceIterator implements Iterator{
private int i = 0;
public boolean end(){
return i == items.length;
}
public Object current(){
return items[i];
}
public void next(){
if(i&ltitems.length)
i++;
}
}

pubic Iterator iterator(){
return new SequenceIterator();
}
}

领域模型(domain object)分为4类:

失血模型

贫血模型 service –> dao –> entity

充血模型 service –> entity <–> dao

胀血模型

简单来说,失血模型就是纯数据POJO,业务逻辑完全与entity分离;贫血模型domain object中含有与持久化无关的逻辑,不依赖于dao层;充血模型把domain object和business object合二为一,service层不依赖dao层,而entity层与dao层形成双向依赖;胀血模型直接取消service层,只剩下entity层与dao层。

由于今年才开始做web开发,之前对各种模型完全没有了解,项目中的模型是由自己瞎摸索出来的。现在总结来看,应该属于贫血模型,但是把大量service层的业务逻辑推到controller层实现,这种方式前期开发相当迅速,但是后期发现严重影响代码重用,不同controller里存在大量重复代码,导致需求更改时需要修改多个controller,影响项目维护。

我感觉目前使用的模型适合小项目的快速开发,但对于大点的项目还是传统的贫血模型更合适。

附:不同模型的优缺点

一、贫血模型

优点:

1、各层单向依赖,结构清楚,易于实现和维护

2、设计简单易行,底层模型非常稳定

3、非常适合于软件外包和大规模软件团队的协作。每个编程个体只需要负责单一职责的小对象模块编写,不会互相影响。

缺点:

1、domain object的部分比较紧密依赖的持久化domain logic被分离到Service层,显得不够OO

2、Service层过于厚重

3、可复用的颗粒度比较 小,代码量膨胀的很厉害,最重要的是业务逻辑的描述能力比较差,一个稍微复杂的业务逻辑,就需要太多类和太多代码去表达

4、对象协作依赖于外部容器的组装,因此裸写代码是不可能的了,必须借助于外部的IoC容器。

二、充血模型

优点:

1、更加符合OO的原则。
2、对象自洽程度很高,表达能力很强,因此非常适合于复杂的企业业务逻辑的实现,以及可复用程度比较高。

3、Service层很薄,只充当Facade的角色,不和DAO打交道。

4、不必依赖外部容器的组装,所以RoR没有IoC的概念。

缺点:

1、DAO和domain object形成了双向依赖,复杂的双向依赖会导致很多潜在的问题。

2、如何划分Service层逻辑和domain层逻辑是非常含混的,在实际项目中,由于设计和开发人员的水平差异,可能导致整个结构的混乱无序。

3、考虑到Service层的事务封装特性,Service层必须对所有的domain object的逻辑提供相应的事务封装方法,其结果就是Service完全重定义一遍所有的domain logic,非常烦琐,而且Service的事务化封装其意义就等于把OO的domain logic转换为过程的Service TransactionScript。该充血模型辛辛苦苦在domain层实现的OO在Service层又变成了过程式,对于Web层程序员的角度来看,和贫血模型没有什么区别了。

4、对象高度自洽的结果是不利于大规模团队分工协作。一个编程个体至少要完成一个完整业务逻辑的功能。对于单个完整业务逻辑,无法再细分下去了。

三、胀血模型

优点:

1、简化了分层

2、也算符合OO

缺点:

1、很多不是domain logic的service逻辑也被强行放入domain object ,引起了domain ojbect模型的不稳定

2、domain object暴露给web层过多的信息,可能引起意想不到的副作用。

参考:

关于架构设计的“贫血模型”与“充血模型”

[转帖]贫血、充血模型的解释以及一些经验

感觉在项目中使用异常进行逻辑控制能使代码逻辑清晰很多,但公司同事应为抛出异常开销的问题一直反对使用异常,最后代码中充斥着大量if else的逻辑判断。

但是在使用一些第三方框架时发现里面很多逻辑就在用异常进行控制,所以一直对异常开销这个问题存在疑问,抽空度娘,得出结论如下:

普通异常开销确实很大,但开销不在于异常的抛出,而是在异常对象创建时开销很大。
创建异常开销大的原因在于异常基类Throwable.java的public synchronized native Throwable fillInStackTrace()方法。

方法介绍:
Fills in the execution stack trace. This method records within this Throwable object information about the current state of the stack frames for the current thread.
性能开销在于:

  1. 是一个synchronized方法(主因)
  2. 需要填充线程运行堆栈信息

解决方法:
自定义异常继承Exception,并重写fillInStackTrace()

1
2
3
4
@Override
public Throwable fillInStackTrace() {
return this;
}

以该异常作为逻辑控制的异常的基类,那么异常性能将大幅度提高,和if else开销相近。

参考:java异常性能问题

1、主扩展模式

主扩展模式,通常用来将几个相似的对象的共有属性抽取出来,形成一个“公共属性表”;其余属性则分别形成“专有属性表”,且“公共属性表”与“专有属性表”都是“一对一”的关系。

感觉主扩展模式适合用于继承关系,“公共属性表”即父类表,“专有属性表”即各个子类各自独有的属性形成的表。主扩展模式用来描述对象内的关系。


2、主从模式

即对象间的一对多关系,例如学校和班级即一种主从模式。


3、名值模式
名值模式,通常用来描述在系统设计阶段不能完全确定属性的对象,这些对象的属性在系统运行时会有很大的变更,或者是多个对象之间的属性存在很大的差异。

我的理解名值模式就是主扩展模式和多对多模式的混合,将主从模式中的“专有属性表”中列变为一张“属性列表“”中的行。“公共属性表”和“属性列表”为多对多关系,属性值可在映射表中。用来描述对象内的关系。
eg:



4、多对多模式

这个没什么好说的,可分为关联表有独立的业务处理需求(映射表除映射功能还有其他属性)和关联表没有独立的业务处理需求(映射表只有映射功能)。多对多模式描述对象间的关系。


总结:

以上4种模式其实就是分别描述了对象内的一对一关系、对象内的多对多关系、对象间的一对多关系,对象间的多对多关系,基本能应用所有情况,其他关系有:
1、对象内的一对多关系:多方会存在冗余信息,将冗余信息提取到一张表后即转换为名值模式;
2、对象间的一对一关系:貌似不存在这种关系,本想如婚姻关系是对象间的一对一关系,但考虑离婚等情况其实也是多对多关系。