1. 創(chuàng)建數(shù)據(jù)庫和表
在小節(jié)UID替換器的實(shí)現(xiàn)中,我們設(shè)計(jì)了一個表。這部分就按照這個表實(shí)現(xiàn)CRUD,表情況如下:
--- table idmapper
id int 規(guī)則ID 自增
name varchar 規(guī)則名
source_id varchar 源ID
direct_id varchar 目的ID
enable int 是否啟用 0:不啟用鼓鲁,1:啟用
這里面數(shù)據(jù)庫采用通用的MySQL數(shù)據(jù)庫,先在MySQL中創(chuàng)建一個名為springdemo的數(shù)據(jù)庫:
CREATE DATABASE springdemo DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
Intellij提供了專門的數(shù)據(jù)庫管理插件港谊,這是界面:
接下來既可以用Intellij提供的數(shù)據(jù)庫工具創(chuàng)建idmapper表骇吭,又可以用SQL創(chuàng)建語句來處理,這里直接使用創(chuàng)建語句創(chuàng)建歧寺。
create table id_mapper(
id INT NOT NULL AUTO_INCREMENT,
name VARBINARY(255) NOT NULL,
source_id VARBINARY(255) NOT NULL,
direct_id VARBINARY(255) NOT NULL,
enable INT DEFAULT 1,
PRIMARY KEY (id)
);
運(yùn)行后的結(jié)果是:
往數(shù)據(jù)庫里面插入兩條數(shù)據(jù)用于測試燥狰,看看結(jié)果:
目前數(shù)據(jù)沒有啥問題了,我們開始按照常用的SpringMVC方法開發(fā)我們的應(yīng)用斜筐。
2. Pojo 對象
由于不使用ORM框架(Mybatis或Hibernate)龙致,我們自己使用簡單的SQL來處理數(shù)據(jù)的CRUD,就需要定義一個Pojo類顷链,同數(shù)據(jù)庫中的表定義對應(yīng)目代。這個類的對象與表中每一行數(shù)據(jù)對應(yīng)。定義這個Pojo類如下:
public class IdMapper {
private int id;
private String name;
private String sourceId;
private String directId;
/**
* enable 狀態(tài)
* 0 表示停用
* 1 表示開啟
*/
private int enable;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSourceId() {
return sourceId;
}
public void setSourceId(String sourceId) {
this.sourceId = sourceId;
}
public String getDirectId() {
return directId;
}
public void setDirectId(String directId) {
this.directId = directId;
}
public boolean getEnable() {
return enable==1;
}
public void setEnable(boolean enable) {
this.enable = enable?1:0;
}
}
3. Pojo對象操作類
有了Pojo對象之后,我們需要實(shí)現(xiàn)這個Pojo對象的操作類像啼。如果用的是Python,使用Sqlalchemy或Django自己的ORM潭苞,然后定義類似的Pojo類就能夠自動實(shí)現(xiàn)對Pojo對象的查詢忽冻。但是Java這方面的功能我還不清楚,應(yīng)該是沒有這么好用的功能此疹,主要原因是Java是靜態(tài)語言僧诚,而這些功能是動態(tài)運(yùn)行時生成的,例如查詢方法蝗碎,像Python和Ruby這樣的腳本語言能夠做到湖笨,而靜態(tài)語言就不容易做到了。
Java的話蹦骑,此處就直接實(shí)現(xiàn)Pojo對象的操作類慈省。按照面向接口編程的原則,我們先設(shè)計(jì)一個IdMapperDao
的接口眠菇,如下:
public interface IdMapperDao {
int insert(final IdMapper idMapper);
IdMapper get(int id);
int update(final IdMapper idMapper);
int delete(int id);
}
然后實(shí)現(xiàn)這個接口:
public class IdMapperDaoImpl extends JdbcDaoSupport implements IdMapperDao {
private final static Logger log=LoggerFactory.getLogger(IdMapperDaoImpl.class);
public int insert(final IdMapper idMapper) {
String sql="insert into idmapper(name, source_id, direct_id, enable) value(?, ?, ?, ?)";
log.info("IdMapperDao insert:" + idMapper.toString());
return this.getJdbcTemplate().update(sql, new PreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps) throws SQLException {
ps.setString(1, idMapper.getName());
ps.setString(2, idMapper.getSourceId());
ps.setString(3, idMapper.getDirectId());
ps.setInt(4, idMapper.getEnable()?1:0);
}
});
}
@Override
public IdMapper get(int id) {
String sql="select * from idmapper where id="+id;
List<IdMapper> list = this.getJdbcTemplate().query(sql, new RowMapper<IdMapper>() {
@Override
public IdMapper mapRow(ResultSet rs, int rowNum) throws SQLException {
return mapRowToIdMapper(rs);
}
});
if(null == list || list.size() == 0) {
return null;
}
return list.get(0);
}
private IdMapper mapRowToIdMapper(ResultSet rs) throws SQLException{
IdMapper idMapper = new IdMapper();
idMapper.setId(rs.getInt("id"));
idMapper.setName(rs.getString("name"));
idMapper.setSourceId(rs.getString("source_id"));
idMapper.setDirectId(rs.getString("direct_id"));
idMapper.setEnable(rs.getInt("enable")==1);
return idMapper;
}
@Override
public int update(final IdMapper idMapper) {
//TODO
return 0;
}
@Override
public int delete(int id) {
//TODO
return 0;
}
}
該類繼承了Spring的JdbcDaoSupport
類边败,這能夠使得與MySQL數(shù)據(jù)庫進(jìn)行數(shù)據(jù)交互的代碼簡單易寫。我們寫好了IdMapper
的操作類捎废,但是并沒有把它關(guān)聯(lián)到數(shù)據(jù)庫笑窜。我們在applicationContext.xml
中進(jìn)行配置,如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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.xsd">
<bean id="mysqlSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="${mysql.db.url}?useUnicode=true&characterEncoding=UTF-8&failOverReadOnly=false&autoReconnect=true" />
<property name="username" value="${mysql.db.user}" />
<property name="password" value="${mysql.db.pwd}" />
<property name="removeAbandoned" value="true"/>
<property name= "testWhileIdle" value="false" />
<property name= "testOnBorrow" value="true" />
<property name= "testOnReturn" value="false" />
<property name= "validationQuery" value="select 1" />
</bean>
<bean id="idMapperDao" class="com.chenyi.learn.dao.impl.IdMapperDaoImpl">
<property name="dataSource" ref="mysqlSource" />
</bean>
</beans>
Spring容器在創(chuàng)建時會根據(jù)applicationContext.xml
中的配置創(chuàng)建Bean登疗。我們設(shè)置的idMapperDao
就是一個Bean排截,其有一個dataSource
屬性指向mysqlSource
的Bean,也就是說把mysqlSource的Bean注入到idMapperDao這個Bean中辐益。而在mysqlSource的Bean里面我們配置了數(shù)據(jù)庫的鏈接參數(shù)断傲。這里面我們需要了解的是,IdMapperDaoImpl
類中并沒有dataSource
這樣的屬性智政,那么是怎么樣實(shí)現(xiàn)注入的呢艳悔?這個問題要源自它的父類JdbcDaoSupport
,查看這個類的源代碼女仰,我們可以發(fā)現(xiàn):
/**
* Set the JDBC DataSource to be used by this DAO.
*/
public final void setDataSource(DataSource dataSource) {
if (this.jdbcTemplate == null || dataSource != this.jdbcTemplate.getDataSource()) {
this.jdbcTemplate = createJdbcTemplate(dataSource);
initTemplateConfig();
}
}
所以猜年,應(yīng)該是通過set方法實(shí)現(xiàn)注入的。
這里面要談一下面向接口編程的好處疾忍。在這里我們提供了Dao的接口乔外,并給了一個實(shí)現(xiàn)類。假設(shè)有一天我們發(fā)現(xiàn)我們存儲的數(shù)據(jù)量已經(jīng)很大了一罩,MySQL的查詢已經(jīng)非常吃力了杨幼。這時候我們考慮使用NoSQL的數(shù)據(jù)庫,例如MongoDB,Hbase等差购。假如之前我們我們沒有用接口四瘫,而是直接寫了一個IdMapperDaoImpl類,而在很多其他代碼里面欲逃,我們直接用了這個類及其對象找蜜。這時候我們要創(chuàng)建一個新的操作類,假設(shè)為IdMapperHbaseDaoImpl稳析,并且我們不得不去修改其他的代碼中的實(shí)現(xiàn)洗做,把IdMapperDaoImpl都替換成IdMapperHbaseDaoImpl---這會造成很多問題。這種情況就是強(qiáng)依賴彰居。而面向接口編程就能做到弱依賴诚纸,解藕。
同樣是上面的情景陈惰,我們這里實(shí)現(xiàn)了一個IdMapperDao的接口畦徘,并在applicationContext.xml
中為這個接口創(chuàng)建了一個Bean。而在其他的代碼里面抬闯,我們依賴接口旧烧,不依賴具體實(shí)現(xiàn)類,使用接口來操作画髓。例如會是以下代碼:
@Autowired
private IdMapperDao dao;
這樣Spring會把實(shí)現(xiàn)了IdMapperDao接口的類注入掘剪,也就是applicationContext.xml
中配置的Bean。然后假設(shè)我們這次要使用IdMapperHbaseDaoImpl了奈虾,在這里只需要把對應(yīng)的Bean改成IdMapperHbaseDaoImpl就行了夺谁,代碼其他部分完全不用更改。這樣肉微,是不是很好啦匾鸥?這就是面向接口編程的優(yōu)勢,我們甚至只需要更改一行配置代碼碉纳,就能換掉底層的DAO具體實(shí)現(xiàn)勿负,而其他部分完全不用更改。
4. Service對象
按照J(rèn)ava Web的開發(fā)方法劳曹,一般來說會對業(yè)務(wù)邏輯進(jìn)行封裝奴愉,而這一層一般叫做service。至于為什么要用service進(jìn)行封裝铁孵,這可以寫很多東西了锭硼。但是此處我們直接按照常用的辦法來,先創(chuàng)建一個Service服務(wù)接口IdMapperService
:
public interface IdMapperService {
IdMapper get(int id);
int insert(IdMapper idMapper);
// Other ...TODO
}
再實(shí)現(xiàn)這個接口IdMapperServiceImpl
:
@Service
public class IdMapperServiceImpl implements IdMapperService {
@Autowired
private IdMapperDao dao;
@Override
public IdMapper get(int id) {
return dao.get(id);
}
@Override
public int insert(IdMapper idMapper) {
return dao.insert(idMapper);
}
}
注意到實(shí)現(xiàn)代碼中使用了@Service
注解蜕劝。這個注解能夠告訴Spring檀头,將為這個類創(chuàng)建一個Bean轰异。另外這段代碼中還使用了@Autowired
注解實(shí)現(xiàn)自動裝配IdMapperDao
對象,這里面體現(xiàn)了前面說的面向接口編程方法暑始,變量dao
是IdMapper
接口類型搭独,具體對象由@Autowired
裝配我們在xml中配置的Bean。Service實(shí)現(xiàn)里面很簡單廊镜,就做個演示牙肝,而一般常見的業(yè)務(wù)里面,Service是相當(dāng)復(fù)雜的期升,例如使用Service封裝事務(wù)。
5. Controller
終于到了最后一步Controller互躬,我們自定義一個IdMapperController
:
@Controller
@RequestMapping("/mapper")
public class IdMapperController {
@Autowired
private IdMapperService service;
@RequestMapping("get")
@ResponseBody
public IdMapper get(@RequestParam int id) {
return service.get(id);
}
}
寫了一個簡單的Controller代碼播赁,主要通過id獲取IdMapper對像。然后我們通過本地測試和URL訪問吼渡,得到下面的結(jié)果:
解釋一下這部分代碼容为。@Controller
注解會告訴Spring也要為這個類生成Bean。@RequestMapping("/mapper")
告訴Spring該類的訪問URL注冊到/mapper
寺酪。而在這個控制器類中坎背,我們用自動注入的方法注入了Service
對像。不像在Service中注入DAO的方式一樣---我們在xml聲明DAO的Bean寄雀,卻沒有在xml中聲明Service的Bean得滤,原因是Service的代碼我們使用了@Service
注解,它會自動為Service生成Bean盒犹,就無需在xml中配置了懂更。
@ResponseBody
注解會將返回對象解析成json格式急膀,這也是我們在瀏覽器中請求沮协,最后會返回json的緣故卓嫂。@RequestParam
注解會將請求URL中的參數(shù)匹配到函數(shù)中的參數(shù),也就是說我們無需寫request.getAttribute("id")
晨雳,就能直接獲取參數(shù)id的值行瑞,這個注解幫助我們搞定這些功能。只能說有了Spring AOP蘑辑,真是方便多了坠宴。
6. 總結(jié)
前前后后花了三個周末的下午總算完成了這三篇練習(xí)洋魂。作為一個剛?cè)隞ava Web坑的 Pythonista,感覺Java文化真是博大精深。語言的不同特征副砍,其相關(guān)技術(shù)棧也很不一樣的衔肢。例如這個小項(xiàng)目角骤,用Python的話心剥,使用Flask框架再加上sqlalchemy插件,一個小時內(nèi)就能弄出來优烧,但是用Java的話……
無意批評Java語言,Java的世界很有趣又沾,我現(xiàn)在領(lǐng)略不深熙卡,以后我會更深入學(xué)習(xí),不限于語言滑燃,技術(shù)棧颓鲜,構(gòu)建方法,框架灾杰,有趣的開源項(xiàng)目……當(dāng)然艳吠,Python我也會繼續(xù)學(xué)習(xí),動態(tài)腳本語言在某些方面優(yōu)勢實(shí)在是明顯的昭娩,更不用說Python這陣子在機(jī)器學(xué)習(xí)栏渺,數(shù)據(jù)分析領(lǐng)域的火熱啦】恼铮總之纹腌,要掌握一門面對對象的語言(Java)升薯,也要掌握好腳本語言(Python/Shell/Js)击困,熟練掌握編程語言這工具,做起事情來才得心應(yīng)手蛛枚。