В данной статье я попробую расписать все шаги, которые потребуются для создания небольшого проекта на Spring Boot и развертывания его на боевом сервере.
Не будем тянуть долгими прелюдиями о философии java и spring'а, и сразу приступим к делу.
Для начала нам необходимо создать каркас приложения, внедрив туда весь необходимый зоопарк технологий(как минимум Spring, JPA, JDBC). До появления spring boot нужно было потратить на это немало времени, если конечно у вас не было рабочей заготовки в закромах кода. И именно сложность создания подобного каркаса, как мне кажется, останавливает многих от разработки небольших веб-проектов на java. Конечно, когда-то был хромой spring roo, который мог создать подобный каркас в ущерб производительности(привет аспектам), но даже с ним количество и сложность конфигурационных файлов заставляли долго медитировать над ними неподготовленного разработчика. Однако теперь с приходом Boot и Spring 4 жизнь стала немного проще и количество конфигурационных файлов заметно уменьшилось.
Итак, каркас, да.
Если у вас есть Intellij Idea 14.1, то проблем с каркасом возникнуть вообще не должно, можно все сделать через специальный мастер создания проектов(File-New-Project...-Spring Initializr). Далее останется только указать названия проектов, выбрать интересующие нас технологии(Web, JDBC, JPA, PostgreSQL) и создать проект.
Если же у вас нет данной IDE, то скачиваем Spring Boot CLI, следуем инструкции в INSTALL.txt. Нужно задать системную переменную SPRING_HOME(путь к папке со Spring Boot, не к папке bin!) и добавить путь к SPRING_HOME/bin в системную переменную PATH на windows.
Итак, консоль спринга настроили, теперь самое время создать проект. Сделать это можно следующей командой:
spring init --dependencies=web,data-jpa,jdbc yourapp
UPDATE
Кроме того, как написали в комментариях, существует еще веб-конструктор: start.spring.io
Далее импортируем получившийся каркас в любимую IDE и начинаем его модифицировать под наши нужды.
Для начала добавим в каталог src/main папку webapps. Все веб-ресурсы мы будем создавать в ней, а не в папке resources, как хочет того спринг. Дело в том, что если мы будем создавать файлы в папке resources, то тогда мы лишимся возможности видеть изменения, сделанные в наших веб-ресурсах, без перезагрузки сервера. А это может быть неприятно, когда ради того, чтобы посмотреть изменившийся текст на веб-странице приходится перезапускать веб-сервер.
Теперь в папке webapps создаем файл index.html и папки css, js, font, images, в которые будем класть соответствующие ресурсы.
Для примера сделаем самый простой каркас index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Yourapp</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<h1> HELLO WORLD </h1>
</body>
</html>
Изменим файл pom.xml
Должно получиться что-то подобное:
<?xml version="1.0" encoding="UTF-8"?>
<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.yourcompany</groupId>
<artifactId>yourapp</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>YourApp</name>
<description></description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>com.yourcompany.Application</start-class>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4-1201-jdbc41</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>validate</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/target/classes/static</outputDirectory>
<resources>
<resource>
<directory>src/main/webapp</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Из pom-файла мы можем увидеть следующее:
Мы используем java 8(самое время ее попробовать). Наш класс приложения называется com.yourcompany.Application(не забудьте переименовать стандартно сгенерированный класс, который может называться к примеру DemoApplication).
Мы используем postgresql 9.4(тоже неплохо бы установить его локально на свою машину). Connection pool для взаимодействия с базой данных мы берем самый модный и производительный (HikariCP). Кроме того, мы используем специальный плагин, который, когда мы будем генерировать итоговый jar'ник, перенесет все наши данные из webapp в resources/static, как того хочет spring boot. В противном случае вы не сможете увидеть все те веб-страницы, что создадите в папке webapps, когда запустите jar-ник.
Добавим пакет config и создадим в нем класс JpaConfig:
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackageClasses = Application.class)
public class JpaConfig implements TransactionManagementConfigurer {
@Value("${dataSource.driverClassName}")
private String driver;
@Value("${dataSource.url}")
private String url;
@Value("${dataSource.username}")
private String username;
@Value("${dataSource.password}")
private String password;
@Value("${hibernate.dialect}")
private String dialect;
@Value("${hibernate.hbm2ddl.auto}")
private String hbm2ddlAuto;
@Bean
public DataSource configureDataSource() {
HikariConfig config = new HikariConfig();
config.setDriverClassName(driver);
config.setJdbcUrl(url);
config.setUsername(username);
config.setPassword(password);
return new HikariDataSource(config);
}
@Bean
public LocalContainerEntityManagerFactoryBean configureEntityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(configureDataSource());
entityManagerFactoryBean.setPackagesToScan("com.yourcompany");
entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
Properties jpaProperties = new Properties();
jpaProperties.put(org.hibernate.cfg.Environment.DIALECT, dialect);
jpaProperties.put(org.hibernate.cfg.Environment.HBM2DDL_AUTO, hbm2ddlAuto);
entityManagerFactoryBean.setJpaProperties(jpaProperties);
return entityManagerFactoryBean;
}
@Bean
public PlatformTransactionManager annotationDrivenTransactionManager() {
return new JpaTransactionManager();
}
}
Кроме того, добавим в файл application.properties следующие строчки:
dataSource.driverClassName=org.postgresql.Driver
dataSource.url=jdbc:postgresql://<ip-адрес сервера, где установлен PostgreSQL>:5432/yourapp_data
dataSource.username=postgres
dataSource.password=
hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
hibernate.hbm2ddl.auto=update
И наконец в Application.java меняем строку инициализации на следующую:
SpringApplication.run(new Class<?>[] {Application.class, JpaConfig.class}, args);
Тем самым мы настроили подключение к СУБД PostgreSQL.
Не забываем создать саму базу данных и простенькую таблицу в ней. Сделать это удобнее всего через PgAdmin.
Создав в ней пустую базу yourapp_data, выполняем скрипт создания таблицы:
CREATE TABLE yourapp_data
(
data_id uuid NOT NULL,
data_description character varying(100) NOT NULL,
CONSTRAINT yourapp_data_pk PRIMARY KEY (data_id)
)
WITH (
OIDS=FALSE
);
ALTER TABLE yourapp_data
OWNER TO postgres;
Теперь настало время немного заняться начинкой нашего проекта. А именно добавить какую-нибудь сущность БД и научиться с ней работать, получая с клиента данные для ее формирования и отправляя клиенту же данные об уже созданных сущностях.
Создаем пакеты controller, entity, repository, service, utils.
В пакете entity создаем интерфейс:
public interface DomainObject extends Serializable {
}
и сущность:
public class Data implements DomainObject {
private UUID id;
private String description;
public Data(UUID id, String description) {
this.id = id;
this.description = description;
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
Аннотации JPA и Hibernate в данном примере использовать не будем, так как эти технологии сильно замедляют работу(запрос может выполняться в 10 раз медленнее, чем на чистом jdbc), а так как у нас нет сильно сложных сущностей, для которых реально может потребоваться ORM, то воспользуемся обычным jdbcTemplate.
Создаем интерфейс репозитория:
public interface DataRepository<V extends DomainObject> {
void persist(V object);
void delete(V object);
Set<String> getRandomData();
}
И его реализацию:
@org.springframework.stereotype.Repository("dataRespitory")
public class DataRepositoryImpl implements DataRepository<Data> {
@Autowired
protected JdbcOperations jdbcOperations;
@Override
public void persist(Data object) {
Object[] params = new Object[] { object.getId(), object.getDescription() };
int[] types = new int[] { Types.VARCHAR, Types.VARCHAR };
jdbcOperations.update("INSERT INTO yourapp_data(\n" +
" data_id, data_description)\n" +
" VALUES (cast(? as UUID), ?);", params, types);
}
@Override
public void delete(Data object) {
jdbcOperations.update("DELETE FROM yourapp_data\n" +
" WHERE data_id = '" + object.getId().toString() + "';");
}
@Override
public Set<String> getRandomData() {
Set<String> result = new HashSet<>();
SqlRowSet rowSet = jdbcOperations.queryForRowSet("SELECT data_description FROM yourapp_data p ORDER BY RANDOM() LIMIT 50;");
while (rowSet.next()) {
result.add(rowSet.getString("data_description"));
}
return result;
}
}
Вместо уже упомянутого jdbcTemplate, мы, как видите, используем JdbcOperations, который является его интерфейсом. Нам приходится использовать везде интерфейсы, отделяя их от реализации, так как, во-первых это стильно, модно, молодежно, а во-вторых, spring в нашем случае использует стандартный jdk'шный Proxy для наших объектов, поэтому напрямую инжектить реализацию не получиться, пока мы не введем полноценные аспекты и AspectJ compile-time weaving. В нашем случае этого и не требуется, чтобы не перегружать приложение.
Осталось уже немного. Создаем наш сервис(мы же хорошие разработчики и должны отделить бизнес-логику от логики работы с СУБД?).
Интерфейс:
public interface DataService {
public boolean persist(String problem);
public Set<String> getRandomData();
}
Реализация:
@Service("dataService")
public class DataServiceImpl implements DataService {
private static final Logger LOG = LoggerFactory.getLogger(DataServiceImpl.class);
@Autowired
@Qualifier("dataRespitory")
private DataRepository dataRepository;
@Override
public boolean persist(String problem) {
try {
dataRepository.persist(new Data(UUID.randomUUID(), problem));
return true;
} catch (Exception e) {
LOG.error("ERROR SAVING DATA: " + e.getMessage(), e);
return false;
}
}
@Override
public Set<String> getRandomData() {
return dataRepository.getRandomData();
}
}
Отлично. Теперь создаем пару вспомогательных классов, необходимых для реализации контроллера:
public class RestException extends Exception {
public RestException() {
}
public RestException(String message) {
super(message);
}
public RestException(String message, Throwable cause) {
super(message, cause);
}
public RestException(Throwable cause) {
super(cause);
}
public RestException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
Это наша реализация Exception'а. Может пригодиться в будущем, хотя и не обязательна, но на нее завязан следующий класс:
@Controller
public class ExceptionHandlerController {
private static final Logger LOG = Logger.getLogger(ExceptionHandlerController.class);
@ExceptionHandler(RestException.class)
public @ResponseBody
String handleException(RestException e) {
LOG.error("Ошибка: " + e.getMessage(), e);
return "Ошибка: " + e.getMessage();
}
}
Если мы словили такую ошибку в нашем контроллере, то она будет обработана дополнительно в этом методе.
Наконец напишем небольшой классик, который будет формировать структуру данных для передачи на клиент:
public class Ajax {
public static Map<String, Object> successResponse(Object object) {
Map<String, Object> response = new HashMap<String, Object>();
response.put("result", "success");
response.put("data", object);
return response;
}
public static Map<String, Object> emptyResponse() {
Map<String, Object> response = new HashMap<String, Object>();
response.put("result", "success");
return response;
}
public static Map<String, Object> errorResponse(String errorMessage) {
Map<String, Object> response = new HashMap<String, Object>();
response.put("result", "error");
response.put("message", errorMessage);
return response;
}
}
Все, со вспомогательными классами закончили. Осталось написать наш контроллер. Он будет простым, как пробка:
@Controller
public class DataController extends ExceptionHandlerController {
private static final Logger LOG = Logger.getLogger(DataController.class);
@Autowired
@Qualifier("dataService")
private DataService dataService;
@RequestMapping(value = "/persist", method = RequestMethod.POST)
public @ResponseBody
Map<String, Object> persist(@RequestParam("data") String data) throws RestException {
try {
if (data == null || data.equals("")) {
return Ajax.emptyResponse();
}
dataService.persist(data);
return Ajax.emptyResponse();
} catch (Exception e) {
throw new RestException(e);
}
}
@RequestMapping(value = "/getRandomData", method = RequestMethod.GET)
public @ResponseBody
Map<String, Object> getRandomData() throws RestException {
try {
Set<String> result = dataService.getRandomData();
return Ajax.successResponse(result);
} catch (Exception e) {
throw new RestException(e);
}
}
}
В нем два метода — сохранить полученные данные и выдать порцию случайных данных на клиент. Контроллер унаследован от созданного нами ранее ExceptionHandlerController. Обработка исключений написана только как шаблон и нуждается в соответствующей доработки под себя.
Итак, основная часть серверного кода написана, осталось проверить его работу на клиенте. Для этого нужно доработать наш файл index.html и заодно добавить библиотеку jquery в каталог js.
index.html:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>YourApp</title>
<script src="js/jquery-2.1.3.min.js"></script>
</head>
<body>
<h1> HELLO WORLD </h1>
<input type="text" id="data"/>
<a id="post" href="#">POST</a>
<a id="get" href="#">GET</a>
<div id="container"></div>
</body>
<script>
$('#get').click(function () {
$.ajax({
type: "GET",
cache: false,
url: '/getRandomData',
data: "",
success: function (response) {
var html = "";
$.each(response.data, function (i) {
html = html + response.data[i] + "<br/>";
});
$('#container').html(html);
}
});
});
$('#post').click(function () {
if (!$("#data").val()) {
alert("Enter your data!");
} else {
$.ajax({
type: "POST",
cache: false,
url: '/persist',
data: {
'data': $("#data").val()
},
success: function (response) {
$('#get').click();
}
});
}
});
</script>
</html>
Да, UI получился не бог весть каким красивым, но зато с его помощью мы можем проверить работу приложения.
Запустим наш проект. В Intellij Idea это можно сделать через специальную конфигурацию запуска(Spring Boot).
Если все сделано верно, то по адресу localhost:8080 вы сможете увидеть заголовок Hello World, строку ввода и две кнопки. Попробуйте ввести что-нибудь в строку ввода и нажать на кнопку POST. Если после этого вы увидите аналогичный текст ниже поля ввода, то все работает как надо. Теперь останется модифицировать проект под свои нужды, добавить модный UI(например materializecss.com) и творить разумное, доброе, вечное.
Однако рано или поздно вы сотворите желаемое и встанет вопрос о том, как донести ваше детище в массы. Об этом будет вторая часть статьи.
Начнем с малого, но важного.
Даже если проект небольшой, все равно для него потребуется свой домен. Если вы просто обкатываете какую-нибудь идею и не хотите тратить бешеные деньги для регистрации домена на том же godaddy, то можете воспользоваться бесплатной альтернативой: freenom.com
Этот сервис позволит бесплатно зарегистрировать домен в зонах .tk, .ml, .ga, .cf, .gq
Да, не самые лучшие зоны, но:
Далее займемся сервером, где все это будет крутиться. Так как проект у нас небольшой, то и сервер нам сгодится небольшой. В идеале хватит VPS. Достать его можно в разных местах, например www.digitalocean.com
Итак, регистрируемся, создаем самый простой дроплет и ставим на него ubuntu (в моем случае это ubuntu 12.04, дальнейшие инструкции буду описывать для этой системы, но на остальных будет примерно то же)
Отлично, у нас есть сервер, пора залить на него наш проект.
Для начала собираем проект maven'ом. Сделать это можно через IDE или же на худой конец зайдя в корневую директорию проекта и введя команду mvn clean install(путь к мавену должен быть прописан в системой переменной path на Windows). После выполнения команды собранный jar'ник помещается в локальный репозиторий (по умолчанию именуемый .m2), откуда его можно стянуть для отправки на сервер.
Для передачи файла на сервер используем WinSCP, если вы работаете под Windows.
Далее заходим на наш сервер, используя putty на Windows или ssh на Linux.
Переходим в директорию, куда был скопирован наш jar-ник и пробуем его запустить командой java -jar youapp.jar
Скорей всего, не получилось. А все почему? Наш проект был создан на java 8, а какая java стоит на сервере, можно узнать с помощью команды java -version. И скорей всего это либо 6, либо 7.
Но не будем унывать, поставим себе новую версию:
sudo add-apt-repository ppa:webupd8team/java
sudo apt-get update
sudo apt-get install oracle-java8-installer
Теперь настала очередь postgres'а. До этого мы использовали локальную версию на машине разработчика, теперь пришло время поставить СУБД на сервер.
Для этого сначала выполняем магическую последовательность команд:
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
sudo apt-get install wget ca-certificates
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install postgresql-9.4 postgresql-contrib-9.4
Запускаем postgres:
sudo service postgresql start
Далее выполняем команду входа в psql:
sudo -u postgres psql
Устанавливаем пароль:
\password postgres
И выходим c помощью команды \q
Редактируем файл /etc/postgresql/9.4/main/postgresql.conf, изменив строчку #listen_addresses = 'localhost' на listen_addresses = '*'
Тем самым мы сможем подключаться к postgresql извне с помощью pgadmin'а. Хотя, конечно, желательно этого избежать в целях безопасности, и когда все будет настроено и отлажено, отключить эту возможность.
Затем редактируем файл /etc/postgresql/9.4/main/pg_hba.conf
Должны быть добавлены две новых строчки и изменена одна строка для 127.0.0.1 следующим образом:
host all all 127.0.0.1/32 trust
host all all <ip-адрес сервера>/32 trust
host all all <ip-адрес машины разработчика>/32 trust
Я намеренно изменил md5 на trust, так как лично у меня были проблемы с запуском проекта, тем самым отключив проверку пароля для заданных адресов. Возможно у вас их не будет.
Теперь все настроено. Хотя тюнинговать постгрес можно до бесконечности, но ведь у нас всего лишь маленький проект, а значит, пока оставим как есть.
Перезапускаем postgres:
sudo service postgresql restart
и проверяем его работу.Всё, с настройкой postgres'а закончили, что у нас дальше по сценарию?
Как уже было отмечено ранее, для запуска собранного jar'ника вполне достаточно команды java -jar youapp.jar
Однако при подобном запуске для того, чтобы зайти на сайт извне, придется прописывать порт(по умолчанию 8080). Чтобы пользователи смогли зайти на сайт, просто введя его адрес, то нам потребуется прокси сервер. В качестве него можно взять nginx, который нужно будет предварительно настроить.
Устанавливаем nginx:
sudo apt-get install nginx
В моем случае корневой директорией nginx была /etc/nginx. Там нам в первую очередь потребуется изменить файл /sites_available/default следующим образом:
server {
listen 80;
server_name youapp.com;
location / {
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:8080/;
}
}
Однако и это еще не все. Необходимо также модифицировать наш проект, чтобы он поддерживал настроенный нами прокси. Благо сделать это не трудно, достаточно лишь в application.properties добавить строки(не забудьте залить новую версию с изменениями):
server.tomcat.remote_ip_header=x-forwarded-for
server.tomcat.protocol_header=x-forwarded-proto
Теперь можно запустить nginx командой service nginx start и затем попробовать запустить наш проект. Он будет доступен по ссылке сайта, либо же, если вы еще не приобрели домен, то по его ip-адресу, без указания порта.
Остался еще один небольшой штрих. Немного неудобно всегда стартовать проект тем способом, который был описан выше. Неплохо бы, чтобы при старте проекта консоль ввода на сервере освобождалась, приложение не закрывалось бы после выхода из ssh-сессии и чтобы где-нибудь велись логи приложения. Сделать это можно с помощью команды nohup. Предварительно создаем bash-скрипт, называя его script.sh:
#!/bin/bash
java -jar youapp.jar
Прописываем ему право на исполнение:
chmod +x ./script.sh
И запускаем командой:
nohup ./start.sh > log.txt 2>&1 &
Все, приложение запущено.
Чтобы остановить приложение, можно либо воспользоваться командой pkill -9 java(при условии, что это единственное java-приложение, запущенное на сервере), либо с помощью утилиты htop, выделив этот процесс, нажав кнопку F9, выбрав слева в списке SIGKILL и нажав enter. На заметку: иногда не срабатывает с первого раза и процедуру приходится повторять.
Теперь, если все сделано правильно, можно открыть сайт нашего проекта в браузере и насладиться результатом.
P.S. Надеюсь, ничего не упустил. Если же найдете ошибку, просьба написать об этом в личном сообщении. Текст будет оперативно исправлен.
P.P.S. Пример небольшого проекта, созданного по описанной выше схеме мной для обкатки технологий, вы можете увидеть здесь: problems.club
Комментарии (37)
dim_s
04.05.2015 17:22+1Я использую gradle для spring-boot, и немного другой скрипт запуска:
chmod +x gradlew nohup ./gradlew bootRun > application.log 2> application.errors.log < /dev/null & PID=$! echo $PID > application.pid
Данный скрипт скидывает pid процесса в файл application.pid, и можно легко остановить приложение:
if [ ! -f application.pid ]; then echo "Server is not running... "; exit 0 fi PID=$(cat application.pid) kill $PID
И я советую все таки использовать gradle, нежели maven, с ним все намного проще. Вот небольшое приложение (исходники) на spring-boot + gradle + шаблонизатор twig github.com/jphp-compiler/j-php.netKelheor Автор
04.05.2015 17:51-1Возможно с gradle и проще(я его не пробовал), но лично я уже давно привык к maven'у, плюс под него всегда можно найти любой плагин, который потребуется.
norguhtar
04.05.2015 17:31+1Мдяя. Где Spring Data? Зачем городить закат солнца в ручную? Второй вопрос зачем вам spring boot если в итоге вы запускаете apache чтобы проксировать туда запросы? Для проксирования уж явно удобнее nginx. Ну а на уровне java лучше уже servlet container взять. Удобнее потом деплоить.
Kelheor Автор
04.05.2015 17:49Где Spring Data?
Всмысле где spring data? А связка JPA+Hibernate и подключение к postgres — это не оно?
В том же Pom.xml есть
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
А почему я не использовал различные аннотации, я написал — все для производительности. Ну не нужно оно тут. Однако если к примеру в приведенном примере аннотировать метод через @Transactional, то это будет работать, через JpaTransactionManager. То есть если что-то конкретно тут не используется, то это не значит, что этого нет.
Зачем городить закат солнца в ручную?
Не совсем понял вопрос +)
Второй вопрос зачем вам spring boot если в итоге вы запускаете apache чтобы проксировать туда запросы?
Всмысле зачем? Я же написал, что если приложение работает на порте, отличном от 80, то потребуется прокси, чтобы не вводить порт вручную. А томкат по умолчанию работает на порте 8080. Конечно, может и можно было бы указать ему явно 80 порт, но к прокси серверу можно прикрутить еще немало интересных вещей, поэтому описал как это сделать.
Для проксирования уж явно удобнее nginx.
Можно и nginx, не спорю. Просто на моем сервере уже стоял настроенный apache2, поэтому привел его для примера. Примером про apache2 я пытался донести мысль, как реагировать, если приложение не будет открываться, когда и сервер вроде запущен, и DNS настроен, а сайт не открывается.
Ну а на уровне java лучше уже servlet container взять. Удобнее потом деплоить.
А по вашему на чем запускается описанное приложение? Embedded tomcat 8. Да, возможно у внешнего томката есть преимущества, но опять таки тут меньше настроек и быстрее развертывание.norguhtar
04.05.2015 18:10+2Всмысле где spring data? А связка JPA+Hibernate и подключение к postgres — это не оно?
Нет. У Spring Data есть вполне конкретное применение. Убирание написания реализации репозитория. Т.е. будет:
import org.springframework.data.jpa.repository.JpaRepository; public interface DataRepository extends JpaRepository<DomainObject, UUID> { }
Вместо того что вы написали. И все. Надо добавить вызов? Добавляем декларативно метод или запрос через аннотацию. Ну и аннотировать не забываем таблицу аннотациями JPA. Это прям в документации Spring Data и написано. Он убирает одно из самых муторных действий при работе со Spring.
Всмысле зачем? Я же написал, что если приложение работает на порте, отличном от 80, то потребуется прокси, чтобы не вводить порт вручную.
Намекаю, apache2 не самый лучший вариант для проксирования.
А по вашему на чем запускается описанное приложение? Embedded tomcat 8. Да, возможно у внешнего томката есть преимущества, но опять таки тут меньше настроек и быстрее развертывание.
Угу, а потом еще одно приложение и еще один отдельный tomcat, я уже молчу про обновление приложения и радости администратора который будет это сопровождать.Kelheor Автор
04.05.2015 18:25Спасибо, теперь я понял, что вы имели ввиду =)
Постараюсь учесть это и вскоре применить на практике.vayho
04.05.2015 22:16+1Там есть еще удобный AbstractPersistable и интеграция с QueryDSL. Если еще не видели посмотрите, да и вообще я бы советовал использовать QueryDSL.
Kelheor Автор
04.05.2015 22:48Удобство это конечно хорошо, но как обстоят дела с производительностью? Как уже говорил, Hibernate+JPA очень сильно проигрывает в производительности тому же jdbcTemplate. А что с QueryDSL? Есть какие-нибудь результаты тестов для сравнения?
vayho
05.05.2015 09:08+1У Hibernate/JPA с производительностью все нормально, если уметь пользоваться. Просто на каждую возникающую проблему есть usecase, например n+1 проблема решается джоинами, либо дополнительным селектом.
Конечно есть ситуации когда приходится сидеть и разбираться что это он там копается и где это он там глючит и так далее. Но спустя время их почти не остается.
QueryDSL это типобезопасный язык запросов, все запросы(если они не динамические) компилируются, а следовательно не возникает проблем с опечатками, с забывчивостью в установке параметров запросов и так далее. Этот DSL может работать как поверх JDBC так и поверх JPA, и даже простых Java коллекций.
Больше всего проблем в связке Hibernate/JPA + Spring Data JPA + QueryDSL занимает Spring Data JPA, потому что API которое он предлагает по умолчанию очень слабое. Запросы пишутся либо с помощью аннотации Query, либо с помощью названия метода
findAllByAccount(String account)
либо с помощью стандартных средств Hibernate/JPA(я использую QueryDSL).
Получается что для простых ситуаций Spring Data подходит но для чего то посложней уже приходится искать более удобные варианты.
Ну и наконец касательно JDBC и производительности в целом, нет ничего лучше чистого JDBC(для Java) для написания запросов в плане производительности, тут у нас полная свобода пишем что хотим управляем кешированием зарпосов и так далее. Но часто нам нужен компромис между скоростью запросов и скоростью написания кода, в таком случае лучше начать с Spring Data и плавно переводить узкие места на JDBC. Например JPA очень плохо(во всяком случае я не нашел) работает с деревьями, нет штатных средств для Closure Table, Nested Sets и т.д. Тут мы используем чистый JDBC(у нас Closure Table в которой храним инфу о дереве).Kelheor Автор
05.05.2015 09:17Я кстати ради интереса делал тест сравнения jdbcTemplate и чистого jdbc-драйвера. Результаты оказались практически одинаковыми(запросы попеременно выполнялись быстрее то на jdbcTemplate, то на jdbc-драйвере, что означает что тут уже на производительность влияла сама база данных).
А из личного опыта, если нагрузка очень большая и данных терабайты, то приходится переписывать код на hibernate на чистый SQL, причем не абы какой, а оптимизированный под конкретную базу данных. А все потому что есть некие тайные знания под каждую базу данных(или ее особенности), которые позволяют ускорить некоторые запросы на порядки. Я не DBA, но на эту тему вроде где-то есть презентация на www.highload.ru для postgres'а. Поэтому да, для проектов, где нагрузка относительно небольшая, можно использовать JPA+Hibernate. Но случись что, потом придется долго и мучительно переписывать код, если вдруг концепция поменяется :)
norguhtar
06.05.2015 06:33Проблема в том что любой ORM это текущая абстракция. Это надо учитывать при реализации и написании запросов.
relgames
11.05.2015 12:02Угу, а потом еще одно приложение и еще один отдельный tomcat, я уже молчу про обновление приложения и радости администратора который будет это сопровождать.
Лучше множество маленьких приложений, чем одна большая JVM.
Маленькое приложение можно рестартануть в случае проблем, с большим могут быть проблематично это сделать.
Да, есть лишние траты на память, но плюсов несравненно больше.
Изолированность, возможность выдерживать нагрузку (если, к примеру, идет нагрузка на определенный компонент, то можно запустить еще 10 нод только с этим компонентом).
Microservices, в общем.
Kelheor Автор
04.05.2015 18:26Спасибо добрым дядям, что напомнили про XSS :)
Использовал StringEscapeUtils.escapeHtml4, но может кто-то знает более эффективные вещи?
Слышал про некий ESAPI от OWASP, но пока не разобрался в каком состоянии эта библиотека и стоит ли ее применять.
AlexP11223
04.05.2015 18:30Intellij Idea 14.1
А там вроде до сих пор не сделали нормальную поддержку Java config'а (вместо xml)?
В смысле, что в MVC будет варнинг о неиспользованном контролере, не будет подсказок во вью и т.п.:(
youtrack.jetbrains.com/issue/IDEA-87346Kelheor Автор
04.05.2015 18:32Сижу на 14.1.2, у меня Java based config, контроллер IDE вполне себе видит, да и все остальное тоже.
AlexP11223
04.05.2015 18:54Странно, у меня тоже 14.1.2, только что проверил и в spring-boot-sample-web-jsp из офф. самплов, и в новом spring boot проекте с web + thymeleaf созданном через Idea, вышеописанные симптомы есть.
Kelheor Автор
04.05.2015 18:59Пардон, видимо я неправильно понял проблему. Думал, она в том, что IDE бины не распознает, а потом посмотрел скриншот и понял, что не прав :)
IDVsbruck
04.05.2015 19:22Продукт идеологически очень нужный и очень востребованный.
Однако настолько сырой, что пользоваться им нормально не представляется возможным… Изучение также начал с простой архитектуры, по учебнику все очень классно получается. Но только начинаешь наращивать функционал, тут же вылазит такое количество нюансов и специфических моментов, что вся эта экономия конфигурации становится просто смешной на фоне трудозатрат по имплементации своего функционала. Я сейчас не возьмусь перечислять все проблемы, решая которые, приходилось извращаться, но побившись об стену и получив кода больше, чем это делаешь традиционным способом, отказался пока от этой идеи. Поглядим за развитием продукта… возможно, чуть позже станет проще. В любом случае, для наращивания опыта и экспериментов использовать можно, но для серьезных проектов этот продукт пока не подходит.Kelheor Автор
04.05.2015 19:43Вполне возможно. Но по идее же spring boot создает чисто каркас с нужными библиотеками и с парой аннотаций, которые подключают те же стандартные аннотации? А дальше уже чистый спринг, с его новомодной java based конфигурацией. Согласен, немного непривычно после xml, но плюс мне видится в том, что конфигурационная логика тут может быть гораздо более гибкой.
Кстати что именно нельзя сконфигурировать таким образом? Можете привести пример? Или вы имеете ввиду какие-нибудь специфичные xml-теги? Но даже если так, то разве нельзя подключить тот же xml при запуске, если вдруг потребуется?IDVsbruck
05.05.2015 01:10Ну, я xml вовсе не рассматривал как альтернативу. У меня была готовая Java конфигурация без единого xml со встроеным сервером (пробовал и Томкет, и Джетти). Я пробовал с нуля заменить ее на Spring Boot. Кроме этого, у меня в конфигурации был Security, а тут у Boot начинаются мраки. Видимо, прийдется заново попробовать настроить, чтобы поднять в памяти все подножки, иначе это бульки на воде.
vaniaPooh
04.05.2015 21:27Еще бы приложение запакетировать в debian пакет и вообще будет здорово или хотя бы стартовать его через upstart \ systemd.
Mirasch
04.05.2015 22:43+1Мы используем Spring Boot как основу для всех наших сервисов. Оказалось очень удобно иметь во всех модулях одинаковую основу для запуска а также не быть зависимым от отдельного Tomcat (Используем Embedded Tomcat) Экономит кучу времени, сил и нервов: не приходиться вспоминать где, как, что нужно делать для запуска, что локально, что на сервере.
akbarovs
05.05.2015 01:38Насколько я помню, вот это все:
config.addDataSourceProperty("cachePrepStmts", "true"); config.addDataSourceProperty("prepStmtCacheSize", "250"); config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); config.addDataSourceProperty("useServerPrepStmts", "true");
для MySQL драйвера, а не для Postgres
darkneo
07.05.2015 08:22По поводу перезагрузки ресурсов без перезагрузки сервера. В офф доке сказано, что достаточно добавить maven-плагин (пруф):
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>springloaded</artifactId> <version>1.2.0.RELEASE</version> </dependency> </dependencies> </plugin>
yetanothercoder
18.05.2015 17:16В идеале хватит VPS. Достать его можно в разных местах, например www.digitalocean.com
раз уж тут и об этом зашла речь то дешевле достать можно тут, в наш век «импортозамещения» особенно )
REZ1DENT3
Спасибо за ссылку на freenom.com, теперь буду использовать
rhamdeew
Аккуратнее. Там через год надо успеть продлить домен в определенное время (период в 2 недели если не ошибаюсь). Если пропустите то придется потом платить деньги.