Myślisz o zostaniu freelancerem? Jak to ładnie brzmi: Być na swoim! Czy będzie dalej tak ładnie, gdy już zacznę pracę na swoje własne konto, dowiem się już niedługo. Aktualnie mam zarejestrowaną firmę i czekam już tylko, aż skończy się okres wypowiedzenia w mojej aktualnej pracy. Wtedy dopiero, będę mógł rozpocząć pracę na kontrakt, który de facto mam już podpisany.
Czas i rzeczywistość zweryfikowały nieco poprawność starej wersji poniższego tekstu. Dlatego też podjąłem się poprawienia i aktualizacji niektórych oraz dodania kilku nowych informacji. Mam nadzieję, że artykuł na tym tylko zyskał.
Jeśli zaś idzie o zadowolenie z pracy na kontrakcie, to po niecałych 2 latach takiej właśnie pracy mogę stwierdzić, że z całą pewnością było warto. Szczególnie, że przeprowadziłem się do Poznania, gdzie rynek pracy jest naprawdę bogaty i jest w czym wybierać, jeśli tylko ma się coś do zaoferowania w zamian.
Polecam na początek założyć konto na GoldenLine i LinkedIn. W ten sposób zobaczycie, jaki jest popyt na ludzi z naszej branży i jakie oferuje się nam stawki. Gdy zostaniecie zasypani ofertami skierowanymi właśnie do takich specjalistów, jak wy, których szuka właśnie jakaś tam wiodąca na rynku firma… nie dajcie się ponieść emocjom. To klasyczna ściema leniwego headhuntera. Tylko nie liczni tak na prawdę czytają dokładnie nasze profile.
To jest temat rzeka. Wszystko zależy od tego, czy będzie się Wam chciało kombinować, czy wolicie iść na łatwiznę. Postaram się poniżej rozebrać ten temat na kilka pomniejszych i pod koniec wrócić do meritum.
VAT możecie płacić, albo nie. Istnieje instytucja zwana przedmiotowe zwolnienie z podatku VAT. Fajna sprawa, bo przy naszych zarobkach zaoszczędzamy 23% naszych dochodów. Minusem jest to, że wszystkie zakupy na firmę pokrywany w pełni z naszej kieszeni i nikt nam różnicy między kwotą netto a brutto nie zwróci. Przywilej zwrotu przysługuje tylko płatnikom VAT.
Zostając płatnikiem VAT nie musimy koniecznie żegnać się z piątą częścią naszego wynagrodzenia. Coraz częściej firmy-pracodawcy oferują zwrot podatku VAT, który będziecie musieli odprowadzić do US. Wygodna sprawa. Szczególnie, że robiąc zakupy na firmę, podatek VAT od zakupionych gratów odliczacie od należności dla fiskusa, zaoszczędzając co nieco mamony.
Innym przypadkiem, moim, jest sytuacja, gdy jestem płatnikiem VAT, ale kontrakt podpisuję z firmą z innego kraju UE. W świetle przepisów UE koszta związane z opłaceniem podatku VAT ponosi firma, dla której taką usługę wykonuję i opłaca ona ten podatek w kraju w którym ma swoją siedzibę. Ja zaś, dalej mam możliwość korzystania ze zwrotu VAT za zakupy na firmę. Jednak US, który będzie musiał mi te pieniądze oddać, będzie zapewne zainteresowany faktem, że to oni mi, a nie ja im płacić muszę. Więc zapewniamy sobie tym kontrolę skarbówki już na samym starcie… ale cóż, oby tylko jedną.
Za radą mojej księgowej korzystam tutaj jednak z innego rozwiązania. Otóż nadpłatę podatku VAT przeksięgowuję na poczet zaliczki na podatek dochodowy. W efekcie w danym miesiącu płacę zaliczkę pomniejszoną o nadpłatę VAT’u. Wydaje mi się, iż US nie cierpi tak bardzo oddając mi pieniążki, bo rozlicza je w ramach zaliczki i może nie będzie nękał mnie tak często, przy każdym większym zakupie.
Drugi próg skali progresywnej zaczyna się u nas od kwoty 85.528 zł (dane na rok 2014). Jeśli więc planujecie zarabiać więcej niż 7100 netto miesięcznie, warto się zastanowić, czy nie przejść na podatek liniowy i zamiast 18% i 32% oddawać jedynie 19%?
Jest to jednak opcja tylko dla leniwych, gdyż jeśli masz głowę na karku i uświadomisz sobie, że są na pewno koszta, które ponosisz na co dzień są tak na prawdę kosztami firmy, obniżysz kwotę przychodu firmy, a tym samym kwotę podatku dochodowego, który będzie trzeba odprowadzić.
Przy zarobkach rzędu 100.000 zł brutto rocznie i kosztach prowadzenia działalności (kosztach uzyskania przychodu) około 200-300 zł netto miesięcznie nie warto w ogóle myśleć o podatku liniowym. Zaś już przy zarobkach rzędu 150.000 zł brutto koszta muszą wynieść przynajmniej 2.500 zł netto, żeby osiągnąć taki sam efekt! Więc jeśli nie planujesz kupować nowego laptopa co miesiąc, może być ciężko zmieścić się w I progu. W takiej właśnie sytuacji lepiej pomyśleć o podatku liniowym. Kalkuluj więc uważnie!
A co może być kosztem firmy? Tutaj bardzo ma duże znaczenie to, co wpiszecie na listę PKD podczas zakładania działalności i jak będziecie gospodarować środkami, które już posiadanie.
Na przykład:
Ważne jest, żeby kosztów nie szukać na siłę, tylko zorientować się, na jakich zasadach pieniądze, które i tak wydajecie na co dzień, można potraktować, jako faktyczne wydatki firmowe. Pilnujcie się jednak, bo jeśli przyjdzie kontrola z US, to może się okazać trudnym do wytłumaczenia, dlaczego ta nowa konsola PS3 została zakupiona na firmę i ma bezpośredni lub pośredni wpływ przychód Waszej firmy. Warto wcześniej poczytać sobie nieco na ten temat!
Jeśli to Wasza pierwsza firma, to przez dwa lata łapiecie się na preferencyjną stawkę. W takiej sytuacji, koszt najniższych miesięcznych składek wyniesie niecałe 500 zł. Po tym okresie zaś około 1.000 zł. Kwoty zaokrąglam nieco, ma to jednak marginalne w tym wypadku znaczenie.
Nie są one duże, a jeśli nie zdecyduje się na bycie VATowcami, to być może nawet żadne. Moim jedynym kosztem, była opłata skarbowa za wydanie poświadczenia, iż zarejestrowałem się jako płatnik VAT. Wszystkie jednak formalności załatwiałem przez internet, przy użyciu certyfikatu kwalifikowanego, który to dostałem od obecnego pracodawcy w prezencie. Możecie jednak np. w US zatroszczyć się o potwierdzenie profilu zaufanego na platformie ePUAP, z pomocą którego zalogujecie się w odpowiednich serwisach, tj. CEIDG.
Załóżmy dla przykładu, że zarabiasz 5.000 zł brutto, czyli około 3.500 zł netto, na umowę o pracę. Chcesz zostać VAT’owcem i pozostać przy progresywnej skali podatkowej. Wszystko po to, żeby zyskać jak najwięcej przywilejów, z których będziesz aktywnie korzystać w celu… wiadomo jakim. Pytanie, w którym momencie zaczynasz wychodzić na plus?
Zakładamy, że chcesz dostać z działalności na rękę tyle samo, co z umowy o pracę. Kosztów miesięcznych będziesz mieć koło 900 zł. Nie jestem teraz jednak pewien, czy ZUS w pełnym wymiarze stanowi koszta, zakładam tutaj jednak że tak.
1
2
3
150 zł na księgowość + 500 zł na ZUS + 250 zł innych kosztów = 900 zł netto kosztów
3.500 zł na rękę + 768 zł zaliczki na podatek + 900 zł netto kosztów = 5.168 zł netto
5.168 zł netto + 23% podatku VAT = 6.712 zł brutto
Do tego będziecie ponosić te same koszta, co dotychczas, plus kilka nowych. Większość z nich da się jednak upchnąć w ramach kosztów uzyskania przychodu. Dzięki temu zapłacicie mniejszą składkę na podatek VAT i podatek dochodowy. W związku z czym zostanie Wam w kieszeni nieco więcej niż te 3.500 zł.
UWAGA! Bierzcie jednak poprawkę na to, że te wyliczenia są bardzo szacunkowe, nie uwzględniają wielu czynników i mają jedynie dać pojęcie o tym, jak to wszystko wygląda, a nie stanowić dokładny kosztorys prowadzenia własnej działalności.
Gdy wybierzesz już ofertę i dostaniesz odpowiedź, musisz dowiedzieć się dokładnie, na jakich warunkach masz pracować. Przy pracy na własnej działalności, gdy podpisujesz kontrakt z inną firmą, nie obowiązują już przepisu Prawa Pracy zmuszające pracodawcę do gwarantowania Ci wielu rzeczy. Tutaj więc musisz uważać, aby kontraktem zabezpieczyć się przed sytuacjami, gdzie firma zatrudniająca Cię, będzie chciała eksploatować Cię po kilkanaście godzin dziennie, siedem dni w tygodniu, w świątek, piątek, czy niedzielę. Zwroty VATu, ubezpieczenia, płatne urlopy, itp. rzeczy są bardzo istotne i mogą stanowić dodatkowy atut przemawiający za danym konkretnym pracodawcą. Ważnym jednak jest również, aby poczytać i popytać ludzi w sieci, może wśród znajomych, czy znają kogoś, kto pracuje dla danej firmy. Papier wiele przyjmie, więc opinia bieżącego lub byłego pracownika może dożo wnieść do oceny kontraktu, który właśnie zamierzasz podpisać.
Mam nadzieję, że moje wypociny się Wam na coś przydały. Mi pozwoliły usystematyzować nieco wiedzę, którą zebrałem podczas kilku tygodni przygotowań, do przejścia na swoje. Życzę Wam powodzenia!
Radość moja jednak zmalała znacznie, gdy podczas aktualizacji przy użyciu TomTom Home dostałem raz, a potem drugi, trzeci… i dwudziesty, mniej więcej podobny do poniższego komunikat błędu ERROR: HomeBase/IO: Error writing file: G:\PNDNavigator
.
Pełen stacktrace:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ERROR: HomeBase/IO: Error writing file: G:\PNDNavigator
@ .\portable\cfile.cpp(1947)
0000045D
in .\portable\cfile.cpp:1947
Stack:
1. chrome://tthome/content/ui/bindings/commit.js:82
error(textOrException);
2. chrome://tthome/content/ui/bindings/ttwizard.xml:1061
this._commitFailed(i, commitPage);
3. chrome://tthome/content/logic/util.js:79
return func.apply(obj, arguments);
4. chrome://tthome/content/logic/dllUtils.js:77
this._errorCallback(e);
5. chrome://tthome/content/logic/dllUtils.js:69
this._errorFatal(error);
Myślę więc sobie, OK zdarza się. Pewnie już ktoś to gdzieś pewnie przerabiał, więc przebrnąłem razem z Googlem przez wszelkie możliwe do znalezienia sposoby ratowania nawigacji, od tych ze strony TomTom’a, a na formatowaniu pamięci przy użyciu specjalistycznego programu dostarczonego przez producenta kości. Rezultat? Kaput!?
NIE! Nie ma tak lekko. Kolejny research podpowiedział mi, że można jeszcze wlutować na płycie głównej nawigacji slot na karty SD, albo nawet sam adapter dla tart microSD. Wszystko byłoby jednak fajnie, gdyby tutorial nie tyczył się poprzednich generacji modelu ONE. Mój niestety ma inną płytę, bez odpowiednich wyprowadzeń.
Totalnie zrezygnowany, gdzieś całkiem niechcący trafiłem na teoretycznie niewiele znaczący post, który sugerował, że problem może wynikać z tego, że na kompie z którego puszcza się aktualizację używa się systemu Windows Vista lub 7 w wersji 64-bit i że warto spróbować odpalić tryb XP, albo przesiąść się na kompa z XP’kiem.
I wiecie co? Cały proces od formatowania, poprzez wgrywanie domyślnej wersji oprogramowania, po aktualizację przebiegł bez najmniejszych zakłóceń. Pozwoliłem sobie nawet, nie bez obawy, podogrywać schematy kolorów, lektorów i kilka innych pierdół. I wszystko to, bez jednego najmniejszego problemu!
]]>Zacznijmy więc przygodę od pobrania i rozpakowania narzędzia SpringSource Tool Suite. Jest to dostosowany do potrzeb Eclipse, więc obsługa nie powinna nastręczać żadnych problemów. Jako, że przy muzyce stanowczo milej się uczy, pracuje i czeka na ściągnięcie 350 megabajtowego archiwum, w tle pobrzękuje sobie Joe Bonamassa 🙂
Po pobraniu pliku, w moim przypadku springsource-tool-suite-2.9.2.RELEASE-e3.7.2-win32.zip, rozpakowujemy go sobie gdzieś na dysku i robimy na Pulpicie skrót do springsource\sts-2.9.2.RELEASE\STS.exe, który będzie wygodniejszy, od skakania po katalogach za każdym razem, gdy zachce się nam go odpalić.
Czy to już koniec? Cóż, jeśli chodzi o IDE i narzędzia to tak. Przygotujemy sobie jednak zawczasu prostą bazę danych.
Żeby nie było za prosto, jako serwer bazy danych wykorzystam maglowanego ostatnio w pracy Firebird’a. Ściągam więc go sobie i instaluje, ale to już chyba każdy zrobić potrafi, więc szczegóły pominę szczególnie, że możecie powalczyć z jakąś swoją ulubioną bazą danych.
Gdy już serwer stoi, tworzymy w nim prostą tabelę naszą testową, czy to z palca, czy z GUI, znaczenia nie ma najmniejszego.
1
2
3
4
5
6
7
8
9
10
11
CREATE TABLE CUSTOMER
(
ID Numeric(18,0) NOT NULL,
EMAIL Varchar(255),
FIRST_NAME Varchar(255),
LAST_NAME Varchar(255),
PRIMARY KEY (ID)
);
GRANT DELETE, INSERT, REFERENCES, SELECT, UPDATE
ON CUSTOMER TO SYSDBA WITH GRANT OPTION;
Dodajemy generator:
1
CREATE GENERATOR CUSTOMER_GEN_SEQ;
Oraz dane testowe:
1
2
INSERT INTO CUSTOMER (ID, FIRST_NAME, LAST_NAME, EMAIL) VALUES ('1', 'Jan', 'Kowalski', 'example.com');
INSERT INTO CUSTOMER (ID, FIRST_NAME, LAST_NAME, EMAIL) VALUES ('2', 'Adam', 'Nowak', 'example1.com');
Zatem zakasamy rękawy i tworzymy nowy projekt, licząc na to, że Java 1.7 z której korzystam nie położy nam framework’a….
Wybieramy zatem Spring MVC Project z menu File -> New -> Spring Template Project
Wygląda na to, że coś się nam tam udało. Teraz trzeba zobaczyć, czy to cudo nam zadziała. Żeby to zrobić, w okienku Servers klikamy prawym na jedynym serwerze i wybieramy Add and Remove… po czym dodajemy nasz nowy projekt Example.
Cacy, pięknie, wciskamy na serwerze Play… i już na dzień dobry mamy babola:
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
SEVERE: Exception sending context destroyed event to listener instance of class org.springframework.web.context.ContextLoaderListener
java.lang.ExceptionInInitializerError
at org.springframework.web.context.ContextLoaderListener.contextDestroyed(ContextLoaderListener.java:144)
at org.apache.catalina.core.StandardContext.listenerStop(StandardContext.java:4819)
at org.apache.catalina.core.StandardContext.stopInternal(StandardContext.java:5466)
at org.apache.catalina.util.LifecycleBase.stop(LifecycleBase.java:232)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:160)
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:895)
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:871)
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:615)
at org.apache.catalina.startup.HostConfig.deployDescriptor(HostConfig.java:649)
at org.apache.catalina.startup.HostConfig$DeployDescriptor.run(HostConfig.java:1585)
at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.util.concurrent.FutureTask$Sync.innerRun(Unknown Source)
at java.util.concurrent.FutureTask.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.IllegalStateException: org.slf4j.LoggerFactory could not be successfully initialized. See also http://www.slf4j.org/codes.html#unsuccessfulInit
at org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:282)
at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:248)
at org.apache.commons.logging.impl.SLF4JLogFactory.getInstance(SLF4JLogFactory.java:155)
at org.apache.commons.logging.impl.SLF4JLogFactory.getInstance(SLF4JLogFactory.java:131)
at org.apache.commons.logging.LogFactory.getLog(LogFactory.java:272)
at org.springframework.web.context.ContextCleanupListener.<clinit>(ContextCleanupListener.java:43)
... 16 more
Research u wujka Google podpowiada nam, że to może być problem z wersją biblioteki Log4j i należy podnieść domyślny numer wersji z 1.2.15 do 1.2.16. W związku z tym należy wyedytować pliczek pom.xml i upewnić się, że numer wersji będzie prawidłowy:
1
2
3
4
5
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
...
Zapisujemy zmiany i magiczny Maven robi za nas całą resztę. Wciskamy więc Play po raz wtóry i okazuje się, że serwer startuje już bez żadnych więcej błędów, a na dodatek, pod odpowiednim adresem mamy działający przykład.
Czy jest kontroler? Otóż adnotacja @Controller informuje Spring’a, że klasa, którą taką adnotacją oznaczyliśmy będzie zawiadywać sposobem przetwarzania i wyświetlania danych w naszym serwisie. Po szczegóły zapraszam do Spring’owego Bloga i artykułu GREEN BEANS: GETTING STARTED WITH SPRING MVC.
Stworzony przez kreatora HomeController zostawiamy sobie w spokoju, a obok niego dodajemy nasz własny CustomerController:
1
2
3
4
5
6
7
8
9
@Controller
public class CustomerController {
@RequestMapping(value = "/customer", method = RequestMethod.GET)
public String home(Locale locale, Model model) {
return "customer_list";
}
}
Oznaczamy klasę adnotacją @Controller i dodajemy metodę home(…). Nazwa metody może być dowolna, znaczenia mają tutaj natomiast: adnotacja @RequestMapping, która mówi kontenerowi jaką ścieżkę adresu metoda owa będzie obsługiwać; lista parametrów, której znaczenie możemy odnaleźć w czeluściach Spring’owego Blog’a; oraz zwracany przez ową metodę String. Nadmienię tylko, iż nie musi być to String, a na przykład instancja klasy ModelAndView… ale to temat nie a ten moment.
Cóż mówi kontenerowi ten zwracany przez nas “customer_list”? Mówi, jaki plik byśmy chcieli wyświetlić. Mogłaby być to ścieżka do pliku, jednak w pliku /Example/src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml mamy domyślnie wygenerowaną konfigurację, która do zwracanego przez nas ciągu znaczków dokleja coś z przodu i coś z tyłu, i w efekcie powstaje nam z tego ścieżka, w której kontener będzie poszukiwał pliku widoku, do którego się odwołać chcemy.
1
2
3
4
5
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
W naszym wypadku będzie to /WEB-INF/views/customer_list.jsp, którego nie mamy, a który trzeba by stworzyć.
1
2
3
4
5
6
7
8
9
10
11
12
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
<h1>A kuku?</h1>
</body>
</html>
Restart serwera, wejście na stronę i naszym oczom ukazuje się:
Wiemy już pi razy drzwi, jak działa kontroler. Teraz przydałoby się zejść nieco głębiej. Spring oferuje nam kilka adnotacji, które pozwalają nam odróżnić poszczególne poziomy funkcjonalności od siebie, a samemu kontenerowi dają też wskazówki, jak powinny one być przez niego traktowane. I tak oto wyróżniamy @Controler, @Service, @Repository i @Component. Szczegóły wynikające z różnic pomiędzy nimi opisuje inny artykuł, jednakże pokrótce można podsumować je tak:
1
2
3
4
5
6
| Annotation | Meaning |
+------------+-----------------------------------------------------+
| @Component | generic stereotype for any Spring-managed component |
| @Repository| stereotype for persistence layer |
| @Service | stereotype for service layer |
| @Controller| stereotype for presentation layer (spring-mvc) |
Dla uproszczenia stworzymy na początku usługę zwracającą zawsze ten sam obiekt, CustomerService.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
public class CustomerService {
private final Customer customer = new Customer("Jan", "Kowalski", "example.com");
public Customer getCustomerById(Long id) {
return customer;
}
public List<Customer> getAllCustomers() {
return Arrays.asList(customer);
}
public Customer createCustomer(String firstName, String lastName, String email) {
return customer;
}
}
Oraz nasze proste DTO w postaci klasy Customer.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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
public class Customer implements Serializable {
/**
*
*/
private static final long serialVersionUID = -7801982418517678583L;
private Long id;
private String firstName;
private String lastName;
private String email;
public Customer() {
}
public Customer(String firstName, String lastName, String email) {
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result
+ ((firstName == null) ? 0 : firstName.hashCode());
result = prime * result
+ ((lastName == null) ? 0 : lastName.hashCode());
result = prime * result + ((email == null) ? 0 : email.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Customer other = (Customer) obj;
if (email == null) {
if (other.email != null)
return false;
} else if (!email.equals(other.email))
return false;
if (firstName == null) {
if (other.firstName != null)
return false;
} else if (!firstName.equals(other.firstName))
return false;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (lastName == null) {
if (other.lastName != null)
return false;
} else if (!lastName.equals(other.lastName))
return false;
return true;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
}
Klasa ToStringBuilder znajduje się w bibliotece commons-lang i żeby ją dodać do projektu, znów musimy wyedytować plik pom.xml, aby dodać do niego nowy wpis:
1
2
3
4
5
6
<!-- Commons -->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.4</version>
</dependency>
Skoro mamy już usługę, która coś potrafi zwrócić, zmodyfikujmy nasz kontroler i plik widoku CustomerController.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Controller
public class CustomerController {
@Autowired
private CustomerService customerService;
@RequestMapping(value = "/customer", method = RequestMethod.GET)
public String home(Locale locale, Model model) {
List<Customer> customers = customerService.getAllCustomers();
model.addAttribute("customers", customers);
return "customer_list";
}
}
W ten sposób kontroler pobierze listę wszystkich udostępnianych przez usługę klientów i przekaże ją do modelu, który z kolei udostępni ją nam w widoku do dalszej obróbki 😉 Również w tym miejscu Spring dostarcza nam kilku adnotacji, a dokładniej @Autowired oraz @Inject, obie opisane tutaj. Cała jednak różnica sprowadza się do tego, iż:
“@Inject does not have a required property unlike Spring’s @Autowire annotation which has a required property to indicate if the value being injected is optional.”
customer_list.jsp:
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
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Customer Home</title>
</head>
<body>
<h1>List of Customers:</h1>
<c:choose>
<c:when test="${customers.size() > 0}">
<table>
<thead>
<tr>
<th>ID</th>
<th>First name</th>
<th>Last name</th>
<th>E-mail</th>
</tr>
</thead>
<tbody>
<c:forEach items="${customers}" var="customer">
<tr>
<td>${customer.id}</td>
<td>${customer.firstName}</td>
<td>${customer.lastName}</td>
<td>${customer.email}</td>
</tr>
</c:forEach>
</tbody>
</table>
</c:when>
<c:otherwise>
No Customers in database
</c:otherwise>
</c:choose>
</body>
</html>
I takim oto sposobem, po restarcie serwera otrzymujemy:
Stworzymy sobie naprędce repozytorium CustomerRepository.java, które z bazy danych wyciągnie nam dodaną zawczasu testową listę klientów.
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
50
51
@Repository
@Transactional(readOnly = true)
public class CustomerRepository {
private JdbcTemplate jdbcTemplate;
@Autowired
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public Customer getCustomerById(Long id) {
return jdbcTemplate.queryForObject("SELECT id, first_name, last_name, email FROM customer WHERE id = ?", new Object[]{id}, new CustomerMapper());
}
public Customer getCustomerByEmail(String email) {
return jdbcTemplate.queryForObject("SELECT id, first_name, last_name, email FROM customer WHERE email = ?", new Object[]{email}, new CustomerMapper());
}
public List<Customer> getAllCustomers() {
return jdbcTemplate.query("SELECT id, first_name, last_name, email FROM customer", new CustomerMapper());
}
@Transactional(readOnly=false)
public Customer storeCustomer(Customer customer) {
if (customer.getId() == null) {
Long newId = jdbcTemplate.queryForObject("SELECT GEN_ID( CUSTOMER_GEN_SEQ, 1) FROM RDB$DATABASE", Long.class);
customer.setId(newId);
jdbcTemplate.update("insert into customer (id, first_name, last_name, email) values (?, ?, ?, ?)", customer.getId(), customer.getFirstName(), customer.getLastName(), customer.getEmail());
} else {
jdbcTemplate.update("update customer set first_name = ?, last_name = ?, email = ? where id = ?", customer.getFirstName(), customer.getLastName(), customer.getEmail(), customer.getId());
}
return getCustomerById(customer.getId());
}
private static final class CustomerMapper implements RowMapper<Customer> {
public Customer mapRow(ResultSet rs, int rowNum) throws SQLException {
Customer customer = new Customer();
customer.setId(rs.getLong("id"));
customer.setFirstName(rs.getString("first_name"));
customer.setLastName(rs.getString("last_name"));
customer.setEmail(rs.getString("email"));
return customer;
}
}
}
Adnotacja @Autowired wstrzyknie nam do metody setDataSource(DataSource dataSource) źródło danych, które skonfigurujemy zaraz poniżej w pliku /Example/src/main/webapp/WEB-INF/spring/root-context.xml. Zadania wewnętrznej klasy CustomerMapper chyba nie muszę omawiać.
Do komunikacji z bazą będziemy używać JdbcTemplate, gdyż jest to nisko poziomowe, klasyczne Spring’owe podejście, a niuansów mieszania JTA, JDBC, Hibernate i transakcji nie chcę w tym wpisie poruszać, gdyż można o nich sobie poczytać tutaj. O samym JDBC zaś tutaj.
root-context.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
<?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:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<!-- Root Context: defines shared resources visible to all other web components -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="org.firebirdsql.jdbc.FBDriver" />
<property name="url" value="jdbc:firebirdsql:localhost/3050:d:\spring-db\first.fdb" />
<property name="username" value="sysdba" />
<property name="password" value="masterkey" />
</bean>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="txManager"/>
</beans>
dataSource odpowiada za konfigurację źródła danych, czyli dostępu do naszej bazy. txManager określa menedżera transakcji, którego wykorzystamy w projekcie. Zaś <tx:annotation-driven… /> informuje kontener, iż sposób zarządzania transakcjami określać będziemy przy użyciu adnotacji, np: @Transactional(readOnly=true).
Żeby jednak taka konfiguracja nam zadziałała znów musimy dodać kilka zależności do pliku pom.xml projektu:
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
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency>
<groupId>org.firebirdsql.jdbc</groupId>
<artifactId>jaybird</artifactId>
<version>2.1.6</version>
</dependency>
<dependency>
<groupId>javax.resource</groupId>
<artifactId>connector-api</artifactId>
<version>1.5</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
Aktualizujemy jeszcze tylko usługę CustomerService.java…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Service
public class CustomerService {
@Autowired
private CustomerRepository customerRepository;
public Customer getCustomerById(Long id) {
return customerRepository.getCustomerById(id);
}
public List<Customer> getAllCustomers() {
return customerRepository.getAllCustomers();
}
public Customer createCustomer(String firstName, String lastName, String email) {
Customer customer = new Customer(firstName, lastName, email);
return customerRepository.storeCustomer(customer);
}
}
…restart serwera i …Shazam!, mamy naszą testową listę klientów prosto z bazy danych.
To tyle, na dzień dzisiejszy. Następnym razem pokażę, jak można coś do naszej bazy wrzucić i wyedytować. Dzięki za uwagę i do następnego!
]]>Zapisać grafikę w osobnym pliku, a w pliku konfiguracyjnym zapisać ścieżkę do grafiki.
Zapisać grafikę bezpośrednio w pliku konfiguracyjnym.
Poniżej przedstawię moje podejście do drugiego z wymienionych przypadków.
W jednej z tworzonych przeze mnie aplikacji pojawiła się konieczność dania użytkownikowi możliwości wybrania grafiki oraz zapisania jej w pliku konfiguracyjnym programu. Jako, że wykorzystywany w aplikacji XMLConfiguration z Apache’owych commons’ów nie daje mi takiej możliwości, doszedłem do wniosku, że najprościej będzie skonwertować posiadany Image do postaci Base64, aby ładnie dało się go zapisać w konfiguracji, ale jako String’a. Oto, co mi z tego wyszło:
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
public void setImage(Image image) throws IOException {
if (image == null) {
conf.setProperty(IMAGE_KEY, IMAGE_DEFAULT_VALUE);
} else {
image = new ImageIcon(image).getImage();
final ByteArrayOutputStream os = new ByteArrayOutputStream();
ImageIO.write(((ToolkitImage) image).getBufferedImage(), "png", os);
final String imageBase64 = Base64.encode(os.toByteArray());
conf.setProperty(IMAGE_KEY, imageBase64);
}
}
public Boolean isImageSelected() {
final String imageBase64 = conf.getString(IMAGE_KEY, IMAGE_DEFAULT_VALUE);
if(StringUtils.isNotBlank(imageBase64)) {
return Boolean.TRUE;
} else {
return Boolean.FALSE;
}
}
public Image getImage() throws IOException {
final String imageBase64 = conf.getString(IMAGE_KEY, IMAGE_DEFAULT_VALUE);
if (imageBase64 == null) {
return null;
}
final byte[] imageBytes = Base64.decode(imageBase64);
final Image image = Toolkit.getDefaultToolkit().createImage(imageBytes);
return new ImageIcon(image).getImage();
}
Zaś zadeklarowanych użytkowników rebelianta zapraszam do odwiedzenia strony, na której Zero Turnaround oferuje za darmo licencję na JRebela na całe 4 m-ce. Swoją drogą, jest to dość podły chwyt marketingowy gdyż, że nie spotkałem jeszcze osoby, która zaczęła by korzystanie z Rebela i mimo, iż nie jest ona darmowy chciała z niego zrezygnować. -> Link do licencji
]]>Narzędzia dostarczone z JDK 1.6, tj. xjc.exe i wsimport.exe, czy też z JBoss’em, tj. wsconsume.exe generują kod bazując na kodowaniu konsolowym i nie dają możliwości zmiany tego kodowania. Nie było to dla mnie zbytnio korzystne, gdyż docelowym kodowaniem w projekcie było UTF-8.
Rozwiązań widziałem kilka. Otóż, można było ręcznie, przy użyciu np: Notepad++, konwertować kodowanie każdego z plików osobno, albo też można było poszukać programu, który przetworzy pliki “wsadowo”. Okazało się jednak, że można nieco na około zmusić JDK i JBossa do użycia wybranego przeze mnie kodowania. Trzeba się niestety nieco “ubabrać” tworząc sobie na podorędziu pomocnicze pliki BAT. Poniżej załączam przykłady pokazujące, jak można stworzyć sobie takie pliki dla narzędzi z JDK, z JBossem postępujemy analogicznie.
Przykładowy plik xjc.bat:
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
ECHO OFF
SETLOCAL
IF "%JAVA_HOME%" == "" (
ECHO System property JAVA_HOME is't set.
EXIT /B
)
IF EXIST %1 (
IF NOT EXIST %2 (
MKDIR %2
ECHO %2 directory was created.
)
ECHO Trying to generate source code from %1 to directory %2
) ELSE (
ECHO Proper usage: xcj.bat file.xsd dest_dir
EXIT /B;
)
SET JAVA_OPTS=%JAVA_OPTS% -Dfile.encoding=utf-8
SET JAVA=%JAVA_HOME%\bin\java
SET XSD_FILE="%1"
SET SRC_DIR="%2"
CALL "%JAVA_HOME%\bin\java" %JAVA_OPTS% -cp "%JAVA_HOME%\lib\tools.jar" com.sun.tools.internal.xjc.XJCFacade -d %SRC_DIR% -no-header %XSD_FILE%
ENDLOCAL
PAUSE
Przykładowy plik wsimport.bat:
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
ECHO ON
SETLOCAL
IF "%JAVA_HOME%" == "" (
ECHO System property JAVA_HOME is't set.
EXIT /B
)
IF EXIST %1 (
IF NOT EXIST %2 (
MKDIR %2
ECHO %2 directory was created.
)
ECHO Trying to generate source code from %1 to directory %2
) ELSE (
ECHO Proper usage: wsimport.bat file.wsdl dest_dir
EXIT /B;
)
SET JAVA_OPTS=%JAVA_OPTS% -Dfile.encoding=utf-8
SET JAVA=%JAVA_HOME%\bin\java
SET WSDL_FILE="%1"
SET SRC_DIR="%2"
CALL "%JAVA_HOME%\bin\java" %JAVA_OPTS% -cp "%JAVA_HOME%\lib\tools.jar" com.sun.tools.internal.ws.WsImport -s %SRC_DIR% -Xnocompile WSDL_FILE%
ENDLOCAL
PAUSE
1
2
3
4
5
...
boolean czyPrawda = sprawdzCzyPrawda();
if (czyPrawda);
wykonajTylkoJesliPrawda();
...
Myślę, że jest to dobry przykład, przemawiający za stosowaniem wypracowanych konwencji i stosowania się nawet do takich detali, jak nawiasy klamrowe dla jednolinijkowego if‘a.
]]>